phil05
phil05

Reputation: 91

How to pass a union as a parameter to a function

This code is for a driver for a DAC chip.

I have a bitfield below which represents a 24-bit register. So what I need to do, is populate the bitfield and write it out over SPI to the chip.

    typedef struct {
        uint8_t rdwr_u8:       1;
        uint8_t not_used_u8:   3;
        uint8_t address_u8:    4;
        uint8_t reserved_u8:   8;
        uint8_t data_u8:       8;
        uint8_t padding_u8:    8;
    } GAIN_REG_st;

In my initialisation function I create a union as below.

    union{
        GAIN_REG_st GAIN_st;
        uint32_t G_32;
    } G_u;

Now I need to pass a GAIN_REG_st bitfield to a function which will populate it.

Once it's populated I can assign the bitfield to a 32-bit integer and pass that integer to a low level function to write over SPI.

How do I pass the bitfield GAIN_REG_st to a function when it's inside a union? (Can you show a function prototype and call)?

How does the function access the bitfield's members? (would it be like G_u.GAIN_st.rdwr_u8 = 1?)

Upvotes: 8

Views: 27103

Answers (5)

Tino
Tino

Reputation: 10459

C89 has no real provision for initializing and passing unions as arguments directly. In C89 Unions can only be declared using variable or variable references and then must be initialized using the variable. However you then can pass and return these variables by value, so without using pointers.

Returned structs and unions need not fit into registers like normal return parameters. In this case the compiler does everything for you (on the stack), and if it is deeply optimizing and inlined, this usually does not involve much overhead (read: Carefully hand optimized C code can be faster, however often the optimizer does a fairly good job).

Below is example code.

With the utility functions create_GAIN_REG_st and GAIN_REG_st_to_G_u you can create the struct/union on the fly on the argument list to the call of your function, which is accepting these as parameter(s).

Please note that this only works for non-pointer-types this way, as pointer must point somewhere. If you use pointers, you need to malloc/free. But with returning the whole datatype you do not need that. One important thing is that you can get a pointer to the data created on the fly, but the data only lives during the call, as usual for variables on the stack - this can quickly lead to "use after free" or pointers aliased to re-used stack regions. (So always avoid pointers to structs/unions which are passed as arguments, which are returned or which are temporary variables.)

#define __inline__  /*if not using GCC*/

typedef struct {
    uint8_t rdwr_u8:       1;
    uint8_t not_used_u8:   3;
    uint8_t address_u8:    4;
    uint8_t reserved_u8:   8;
    uint8_t data_u8:       8;
    uint8_t padding_u8:    8;
} GAIN_REG_st;

typedef union {
    GAIN_REG_st GAIN_st;
    uint32_t G_32;
} G_u;

GAIN_REG_st __inline__
create_GAIN_REG_st(uint8_t rdwr, uint8_t address, uint8_t data)
{
  GAIN_REG_st g = { 0 };
  g.rdwr_u8 = rdwr;
  g.address_u8 = address;
  g.data_u8 = data;
  return g;
}

G_u __inline__
GAIN_REG_st_to_G_u(GAIN_REG_st g)
{
  G_u u = { 0 };
  u.GAIN_st = g;
  return u;
}

Now you can directly call your function spi:

void
spi(G_u u)
{
  if (u.GAIN_st.rdwr_u8)
    {
      /* implement rdwr_u8==1 here */
    }
  else
    {
      /* implement rdwr_u8==1 here */
    }
}

int
main()
{
  spi(GAIN_REG_st_to_G_u(create_GAIN_REG_st(1,19,255)));
  return 0;
}

Of course you can flatten out the double call:

G_u __inline__
create_G_u_bits(uint8_t rdwr, uint8_t address, uint8_t data)
{
  return GAIN_REG_st_to_G_u(create_GAIN_REG_st(rdwr, address, data));
}

int
main()
{
  spi(create_G_u_bits(1,19,255));
  return 0;
}

or you can create a specialized function:

void
spi_bits(uint8_t rdwr, uint8_t address, uint8_t data)
{
  spi(GAIN_REG_st_to_G_u(create_GAIN_REG_st(rdwr, address, data)));
}

int
main()
{
  spi_bits(1,19,255);
  return 0;
}

Upvotes: 0

Clifford
Clifford

Reputation: 93476

The title is about passing the union, but the question asks about passing structure. You can do either, you simply declare the function argument accordingly and pass either the member or the whole union.

All the current answers demonstrate passing by reference, but there is no need to do that, you can pass by copy, which for a struct or union that is ultimately a 32bit value is no more expensive and saves a dereferencing in the called function, so may be more efficient;

void fn( union G_u arg );

int main()
{
    union G_u param;
    ...
    fn( param );
}

or to pass the structure:

void fn( GAIN_REG_st arg );

int main()
{
    GAIN_REG_st param;
    ...
    fn( param );
}

Note that you could typedef the union as you have the struct:

typedef union 
{
    GAIN_REG_st GAIN_st;
    uint32_t    G_32;
} G_u ;

then the first example would be simply;

void fn( G_u arg );

int main()
{
    G_u param;
    ...
    fn( param );
}

There will almost certainly be no difference in overhead between passing the struct or the union. It is just a matter of where you choose to expose knowledge of the internal representation.

Upvotes: 6

MByD
MByD

Reputation: 137312

Prototype:

int populateGainRegSt(GAIN_REG_st *st);

The function may access the structure fields using -> operator:

st->data_u8 = 1;

Usage:

G_u g;
...
populateGainRegSt( &(g.GAIN_st));

Upvotes: 1

user405725
user405725

Reputation:

#include <stdint.h>
#include <stdio.h>

typedef struct
{
    uint8_t rdwr_u8:       1;
    uint8_t not_used_u8:   3;
    uint8_t address_u8:    4;
    uint8_t reserved_u8:   8;
    uint8_t data_u8:       8;
    uint8_t padding_u8:    8;
} GAIN_REG_st;

union G_u
{
    GAIN_REG_st GAIN_st;
    uint32_t    G_32;
};

static void foo (GAIN_REG_st *data)
{
    printf ("%x %x %x\n",
        data->rdwr_u8,
        data->not_used_u8,
        data->address_u8);
}

int main ()
{
    union G_u udata;
    udata.G_32 = 1986;
    foo (&udata.GAIN_st);
    return 0;
}

Upvotes: 0

user82238
user82238

Reputation:

union G_u
  the_union;

the_union.GAIN_st.address_u8 = 0x4;

function_call( &the_union );

void function_call( union G_u *the_union )
{
    uint8
        address;

    address = the_union->GAIN_st.address_u8;

    return;
}

Do you mean this? it's a union, so why pass an internal member? it won't make any difference. They all start at the same memory offset.

Upvotes: 8

Related Questions