Is it possible to declare unions inside of a structure in C?
Asked Answered
L

2

5

I wanted to shorten my code, the code's purpose is to control stepper motors. I want to hold the ammount of steps (32 bits) and the frequency of rotation (16 bits). I receive this information through a bus in a uint8_t format. My idea was to not have to multiply each bit by 256/65535.... to make up the steps and frequency. I can do this with unions, but I also want to have multiple motors, so I decided to declare a structure for that and have the unions inside. It keeps giving me errors, so I am obviously doing something incorrectly.

I expected that declaring the unions inside the structure would not be an issue, as unions take up the memory space equal to it's largest member, it seemed reasonable that they could be structure elements. Here is a code snippet:

struct Stepper_Motor
  {
        union Num_Steps
        {
            uint32_t Sum_Steps;
            uint8_t Arr_Steps[4];
        };

        union Rotation_freq
        {
            uint16_t Sum_Freq;
            uint8_t Arr_Freq[2];
        };

        uint8_t State;
  };

When I try to access the struct members after declaring it, the IDE gives me a list of the members of the structure, when I write one of them down:

```
struct Stepper_Motor Motor1,Motor2,Motor3;

//Some code... //

Motor1.Arr_Freq[0] = something;  // this gives me an error,  "no members named Arr_Freq"
```

I also tried the following:

Motor1.Rotation_freq.Arr_Freq[0] = something;  //error

Is it even possible to do what I want? Do I have to declare the unions outside the structure then refer to them inside of it, if so, how? Is this a bogus way of using unions, to save on having to write multiplications?

Livorno answered 11/1, 2023 at 22:3 Comment(2)
The solution in the reply below worked, I did try it. Motor1.Rotation_freq.Arr_Freq[0] = something; , did not work it kept not recognizing the union member.Livorno
@Livorno I reiterate that you need to show us the complete and unedited text of the error messages.Hamamelidaceous
H
2

The problem with your original declaration

struct Stepper_Motor
{
    union Num_Steps
    {
        uint32_t Sum_Steps;
        uint8_t Arr_Steps[4];
    };
    // ...
};

is that it declares a type, union Num_Steps, but does not declare a struct field that has that type. You should have gotten a warning message like warning: declaration does not declare anything for both unions, in addition to the hard error (along the lines of error: 'struct Stepper_Motor' has no member named 'Rotation_freq') on the Motor1.Rotation_freq.Arr_Freq[0] = something; line. (This is why I made such a fuss in the comments about wanting to see the complete and unedited text of the error messages. Very often in C, the diagnostic that actually tells you what the problem is, is not the diagnostic that looks most important to an inexperienced user of the language.)

In addition to dbush's suggestion to use anonymous unions, you can use named fields with an anonymous type:

struct Stepper_Motor
{
    union
    {
        uint32_t Sum_Steps;
        uint8_t Arr_Steps[4];
    } Num_Steps;

    union
    {
        uint16_t Sum_Freq;
        uint8_t Arr_Freq[2];
    } Rotation_freq;

    uint8_t State;
};

Or you can adopt the C++ convention of not using the tag namespace:

typedef union Num_Steps_u {
    uint32_t Sum_Steps;
    uint8_t Arr_Steps[4];
} Num_Steps_u;

typedef union Rotation_freq_u {
    uint16_t Sum_Freq;
    uint8_t Arr_Freq[2];
} Rotation_freq_u;

typedef struct Stepper_Motor {
    Num_Steps_u Num_Steps;
    Rotation_freq_u Rotation_freq;
    uint8_t State;
} Stepper_Motor;

Either one will make the rest of your original code work. Note that if you pick the C++-alike route you cannot have the name of the type be the same as the name of the field, and there are a number of other annoying traps that would take me too long to explain -- for instance, there is a very good reason why I wrote Thing twice in each instance of typedef union Thing { ... } Thing; but it would take me an entire essay about ABIs and name mangling to explain it. So I recommend you actually use dbush's suggestion if possible, or the named fields with anonymous types if your compiler doesn't support C2011 anonymous union members.

Hamamelidaceous answered 12/1, 2023 at 15:0 Comment(1)
Seems like I need to look into structures and unions. Very thorough answer. You described exactly want I was seeing in my code in terms of errors/warnings.Livorno
N
8

If you remove the tag name from the unions so that they're anonymous:

struct Stepper_Motor
{
        union
        {
            uint32_t Sum_Steps;
            uint8_t Arr_Steps[4];
        };

        union
        {
            uint16_t Sum_Freq;
            uint8_t Arr_Freq[2];
        };

        uint8_t State;
};

Then the union members will appear as members of the struct.

Neonatal answered 11/1, 2023 at 22:6 Comment(3)
That fixed the issue, and it works perfectly now. Didn't know you can have anonymous unions, thank you!Livorno
Alternatively, give the union members names, with or without the tags. Then one would access the members of each union via the corresponding structure member.Ghiselin
@Livorno It's a relatively new C feature from C11. So you'll need an up to date standard compliant compiler (like gcc >5.0.0).Spiny
H
2

The problem with your original declaration

struct Stepper_Motor
{
    union Num_Steps
    {
        uint32_t Sum_Steps;
        uint8_t Arr_Steps[4];
    };
    // ...
};

is that it declares a type, union Num_Steps, but does not declare a struct field that has that type. You should have gotten a warning message like warning: declaration does not declare anything for both unions, in addition to the hard error (along the lines of error: 'struct Stepper_Motor' has no member named 'Rotation_freq') on the Motor1.Rotation_freq.Arr_Freq[0] = something; line. (This is why I made such a fuss in the comments about wanting to see the complete and unedited text of the error messages. Very often in C, the diagnostic that actually tells you what the problem is, is not the diagnostic that looks most important to an inexperienced user of the language.)

In addition to dbush's suggestion to use anonymous unions, you can use named fields with an anonymous type:

struct Stepper_Motor
{
    union
    {
        uint32_t Sum_Steps;
        uint8_t Arr_Steps[4];
    } Num_Steps;

    union
    {
        uint16_t Sum_Freq;
        uint8_t Arr_Freq[2];
    } Rotation_freq;

    uint8_t State;
};

Or you can adopt the C++ convention of not using the tag namespace:

typedef union Num_Steps_u {
    uint32_t Sum_Steps;
    uint8_t Arr_Steps[4];
} Num_Steps_u;

typedef union Rotation_freq_u {
    uint16_t Sum_Freq;
    uint8_t Arr_Freq[2];
} Rotation_freq_u;

typedef struct Stepper_Motor {
    Num_Steps_u Num_Steps;
    Rotation_freq_u Rotation_freq;
    uint8_t State;
} Stepper_Motor;

Either one will make the rest of your original code work. Note that if you pick the C++-alike route you cannot have the name of the type be the same as the name of the field, and there are a number of other annoying traps that would take me too long to explain -- for instance, there is a very good reason why I wrote Thing twice in each instance of typedef union Thing { ... } Thing; but it would take me an entire essay about ABIs and name mangling to explain it. So I recommend you actually use dbush's suggestion if possible, or the named fields with anonymous types if your compiler doesn't support C2011 anonymous union members.

Hamamelidaceous answered 12/1, 2023 at 15:0 Comment(1)
Seems like I need to look into structures and unions. Very thorough answer. You described exactly want I was seeing in my code in terms of errors/warnings.Livorno

© 2022 - 2024 — McMap. All rights reserved.