Forums

Initializing a struct in a union (or not, as the case might be)

Started by MaxMaxfield 1 month ago21 replieslatest reply 1 month ago70 views
I'm afraid I've reached the limit of what I can wrap my brain around with regards to initializing a union -- any help here will be very gratefully received.

As you can see in the following Arduino sketch, I have a union called 'Settings' that contains (a) a struct called 'vds' containing 10 byte-size variables and (b) an array called 'vda' containing 10 bytes.

I then declare two variables of this union type: 'Defsettings' ("Default Settings") and 'WrkSettings' (Working Settings).

What I’m trying to do is initialize the named variables in the struct in the DefSettings union as shown below – but my compiler barfs – can you PLEASE tell me what I’m doing wrong (said Max, plaintively)?



#define NUM_SET_BYTES      10

typedef union Settings
{
  struct
  {
    uint8_t vdMagicNum;
    uint8_t vdVersionNum;
    uint8_t vdTime;
    uint8_t vdDate;
    uint8_t vdNumModes;
    uint8_t vdNumFx;
    uint8_t vdFxMode00;
    uint8_t vdFxMode01;
    uint8_t vdDxMode02;
    uint8_t vdChecksum;
  } vds;

  uint8_t vda[NUM_SET_BYTES];
};

Settings DefSettings =
    {
        .vds.vdMagicNum    = 0x42,
        .vds.vdVersionNum  = 0x11,
        .vds.vdTime        = 0x00,
        .vds.vdDate        = 0x00,
        .vds.vdNumModes    = 0x03,
        .vds.vdNumFx       = 0x0F,
        .vds.vdFxMode00    = 0x00,
        .vds.vdFxMode01    = 0x00,
        .vds.vdDxMode02    = 0x00,
        .vds.vdChecksum    = 0x00
    };

Settings WrkSettings;


void setup ()
{
    Serial.begin (9600);

    // Copy the default settings into working settings
    for (int i = 0; i < NUM_SET_BYTES; i++)
    {
        WrkSettings.vda[i] = DefSettings.vda[i];
    }    

    // Check to see the copy worked
    for (int i = 0; i < NUM_SET_BYTES; i++)
    {
        Serial.print(i);
        Serial.print("   ");
        Serial.println(WrkSettings.vda[i]);
    }

    while(1);
}


void loop ()
{

}

[ - ]
Reply by Bob11June 24, 2021

Works fine for me with gcc if I move the typename 'Settings' to before the ending semicolon where it belongs:

typedef union
{
  struct
  {
    uint8_t vdMagicNum;
    uint8_t vdVersionNum;
    uint8_t vdTime;
    uint8_t vdDate;
    uint8_t vdNumModes;
    uint8_t vdNumFx;
    uint8_t vdFxMode00;
    uint8_t vdFxMode01;
    uint8_t vdDxMode02;
    uint8_t vdChecksum;
  } vds;

  uint8_t vda[NUM_SET_BYTES];
} Settings;

[ - ]
Reply by MaxMaxfieldJune 24, 2021
I tried making your suggested change as shown below -- but I'm still getting the errors shown below that (sad face)


#define NUM_SET_BYTES      10

typedef union
{
  struct
  {
    uint8_t vdMagicNum;
    uint8_t vdVersionNum;
    uint8_t vdTime;
    uint8_t vdDate;
    uint8_t vdNumModes;
    uint8_t vdNumFx;
    uint8_t vdFxMode00;
    uint8_t vdFxMode01;
    uint8_t vdDxMode02;
    uint8_t vdChecksum;
  } vds;

  uint8_t vda[NUM_SET_BYTES];
} Settings;

Settings DefSettings =
    {
        .vds.vdMagicNum    = 0x42,
        .vds.vdVersionNum  = 0x11,
        .vds.vdTime        = 0x00,
        .vds.vdDate        = 0x00,
        .vds.vdNumModes    = 0x03,
        .vds.vdNumFx       = 0x0F,
        .vds.vdFxMode00    = 0x00,
        .vds.vdFxMode01    = 0x00,
        .vds.vdDxMode02    = 0x00,
        .vds.vdChecksum    = 0x00
    };

Settings WrkSettings;


void setup ()
{
    Serial.begin (9600);

    for (int i = 0; i < NUM_SET_BYTES; i++)
    {
        WrkSettings.vda[i] = DefSettings.vda[i];
    }    

    for (int i = 0; i < NUM_SET_BYTES; i++)
    {
        Serial.print(i);
        Serial.print("   ");
        Serial.println(WrkSettings.vda[i]);
    }

    while(1);
}


void loop ()
{

}


--------- Error messages follow ----

Test_01:33:9: error: expected primary-expression before '.' token
         .vds.vdMagicNum    = 0x42,
         ^
Test_01:34:9: error: expected primary-expression before '.' token
         .vds.vdVersionNum  = 0x11,
         ^
Test_01:35:9: error: expected primary-expression before '.' token
         .vds.vdTime        = 0x00,
         ^
Test_01:36:9: error: expected primary-expression before '.' token
         .vds.vdDate        = 0x00,
         ^
Test_01:37:9: error: expected primary-expression before '.' token
         .vds.vdNumModes    = 0x03,
         ^
Test_01:38:9: error: expected primary-expression before '.' token
         .vds.vdNumFx       = 0x0F,
         ^
Test_01:39:9: error: expected primary-expression before '.' token
         .vds.vdFxMode00    = 0x00,
         ^
Test_01:40:9: error: expected primary-expression before '.' token
         .vds.vdFxMode01    = 0x00,
         ^
Test_01:41:9: error: expected primary-expression before '.' token
         .vds.vdDxMode02    = 0x00,
         ^
Test_01:42:9: error: expected primary-expression before '.' token
         .vds.vdChecksum    = 0x00
         ^
exit status 1
expected primary-expression before '.' token


[ - ]
Reply by matthewbarrJune 25, 2021

I duplicated Bob11's result with gcc. It appears you've found a case that the Arduino compiler doesn't handle well, good that you have a workaround courtesy VadimB!

[ - ]
Reply by MaxMaxfieldJune 25, 2021

It's heartening that I was almost there the first time -- all I needed was (a) Bob11's slight tweak and (b) a compiler that worked LOL. But it's awesome that VadimB's workaround... worked :-)

[ - ]
Reply by VadimBJune 24, 2021

The first error (well, the second) I get with arm-none-eabi-gcc is:

init.c:24:1: error: unknown type name 'Settings'; use 'union' keyword to refer to the type
   24 | Settings DefSettings =
      | ^~~~~~~~
      | union

Exactly what errors does your compiler give? What is the version of the compiler?

[ - ]
Reply by MaxMaxfieldJune 24, 2021

I'm using whatever compiler is in the Arduino IDE version 1.8.13 


The errors I'm getting are:


Test_01:33:9: error: expected primary-expression before '.' token
         .vds.vdMagicNum    = 0x42,
         ^
Test_01:34:9: error: expected primary-expression before '.' token
         .vds.vdVersionNum  = 0x11,
         ^
Test_01:35:9: error: expected primary-expression before '.' token
         .vds.vdTime        = 0x00,
         ^
Test_01:36:9: error: expected primary-expression before '.' token
         .vds.vdDate        = 0x00,
         ^
Test_01:37:9: error: expected primary-expression before '.' token
         .vds.vdNumModes    = 0x03,
         ^
Test_01:38:9: error: expected primary-expression before '.' token
         .vds.vdNumFx       = 0x0F,
         ^
Test_01:39:9: error: expected primary-expression before '.' token
         .vds.vdFxMode00    = 0x00,
         ^
Test_01:40:9: error: expected primary-expression before '.' token
         .vds.vdFxMode01    = 0x00,
         ^
Test_01:41:9: error: expected primary-expression before '.' token
         .vds.vdDxMode02    = 0x00,
         ^
Test_01:42:9: error: expected primary-expression before '.' token
         .vds.vdChecksum    = 0x00
         ^
exit status 1
expected primary-expression before '.' token

[ - ]
Reply by VadimBJune 24, 2021

Can you try it this way:

Settings DefSettings = {
    .vds = {
        .vdMagicNum    = 0x42,
        .vdVersionNum  = 0x11,
        .vdTime        = 0x00,
        .vdDate        = 0x00,
        .vdNumModes    = 0x03,
        .vdNumFx       = 0x0F,
        .vdFxMode00    = 0x00,
        .vdFxMode01    = 0x00,
        .vdDxMode02    = 0x00,
        .vdChecksum    = 0x00
    }
};


[ - ]
Reply by MaxMaxfieldJune 24, 2021

O-M-G! That works!!! Thank you!!!


[ - ]
Reply by VadimBJune 24, 2021

You are welcome!

[ - ]
Reply by DilbertoJune 24, 2021

Hi, Max!


What about to try the following:
( I don't know if it works, but at least, it compiles!     :-)


#define NUM_SET_BYTES      10

union Settings

{

  struct

  {

    uint8_t vdMagicNum;

    uint8_t vdVersionNum;

    uint8_t vdTime;

    uint8_t vdDate;

    uint8_t vdNumModes;

    uint8_t vdNumFx;

    uint8_t vdFxMode00;

    uint8_t vdFxMode01;

    uint8_t vdDxMode02;

    uint8_t vdChecksum;

  } vds;

  uint8_t vda[NUM_SET_BYTES];

};

Settings DefSettings =

    {

        0x42,

        0x11,

        0x00,

        0x00,

        0x03,

        0x0F,

        0x00,

        0x00,

        0x00,

        0x00,

    };

Settings WrkSettings;

void setup() {

  // put your setup code here, to run once:

    Serial.begin (9600);

    // Copy the default settings into working settings

    for (int i = 0; i < NUM_SET_BYTES; i++)

    {

        WrkSettings.vda[i] = DefSettings.vda[i];

    }    

    // Check to see the copy worked

    for (int i = 0; i < NUM_SET_BYTES; i++)

    {

        Serial.print(i);

        Serial.print("   ");

        Serial.println(WrkSettings.vda[i]);

    }

    while(1);

}

void loop() {

  // put your main code here, to run repeatedly:

}

[ - ]
Reply by MaxMaxfieldJune 24, 2021

Hi Dilberto -- VadimB came up with a solution that works:

Settings DefSettings = {
    .vds = {
        .vdMagicNum    = 0x42,
        .vdVersionNum  = 0x11,
        .vdTime        = 0x00,
        .vdDate        = 0x00,
        .vdNumModes    = 0x03,
        .vdNumFx       = 0x0F,
        .vdFxMode00    = 0x00,
        .vdFxMode01    = 0x00,
        .vdDxMode02    = 0x00,
        .vdChecksum    = 0x00
    }
};

[ - ]
Reply by tcfkatJune 25, 2021

Hello Max!

1) You don't need a union. To determine the size the sizeof()-operator is there. This is also more flexible and less error-prone than a static #define!

2) You don't need a copy loop, memcpy() is made for this and in general more efficient.

My suggestion:

#include <stdint.h> // for uint8_t
#include <string.h> // for memcpy()
#include <stdio.h>  // for printf()
 
typedef struct
  {
    uint8_t vdMagicNum;
    uint8_t vdVersionNum;
    uint8_t vdTime;
    uint8_t vdDate;
    uint8_t vdNumModes;
    uint8_t vdNumFx;
    uint8_t vdFxMode00;
    uint8_t vdFxMode01;
    uint8_t vdDxMode02;
    uint8_t vdChecksum;
} Settings;
 
Settings WrkSettings,
         DefSettings = {
            .vdMagicNum    = 0x42,
            .vdVersionNum  = 0x11,
            .vdTime        = 0x00,
            .vdDate        = 0x00,
            .vdNumModes    = 0x03,
            .vdNumFx       = 0x0F,
            .vdFxMode00    = 0x00,
            .vdFxMode01    = 0x00,
            .vdDxMode02    = 0x00,
            .vdChecksum    = 0x00
         };
 
int main (void)
{
    // copy DefSettings into WrkSettings
    memcpy (&WrkSettings, &DefSettings, sizeof(Settings));
    // check
    for (int i = 0; i < sizeof(Settings); i++)
        printf ("%i   0X%02X\n", i, *((uint8_t*) &WrkSettings + i));
    return 0;
}

Best regards, Eric

[ - ]
Reply by MaxMaxfieldJune 25, 2021

Hi Eric -- (a) I love this solution and (b) I wish I was more proficient at C/C++

The problem is that I'm writing an article about a display project I'm building (see the follow-up question I'm poised to pose to this forum) and my readers know less than I (if that's possible) -- I'm building things up step-by-step and this particular problem lent itself to ability of the union to look at the same area of memory in two different ways. Also, as part of this I'll be copying the settings back and forth between EEPROM and main memory, and I don't think memcpy() will work with EEPROM (I'll have to think about this) -- Max

[ - ]
Reply by VadimBJune 25, 2021

You don't even need memcpy and sizeof. The following will do it for you:

#include

...

void main (void)

{

    WrkSettings = DefSettings;

}

Advantage: just one line of code.

Drawback: call to memcpy() under the hood to move 11 bytes. The function itself is 0x134 bytes long (in my libc which comes with gcc 9.3.1).

And I can continue with "make DefSettings const, so it will stay in .text and not in .data", etc, etc.

[ - ]
Reply by MaxMaxfieldJune 25, 2021

But what if I want to copy the data from DefSettings to the EEPROM -- or from the EEPROM to WrkSettings? For the former (with my union), I was planning on using:

    for (int i = 0; i < NUM_SET_BYTES; i++)
    {
        EEPROM.write(i, DefSettings.vda[i]);
    }

Max

[ - ]
Reply by tcfkatJune 25, 2021

Max, memcpy does not work with the EEPROM, this should be clear. The EEPROM needs its proper write routines.

But you still don't need a union. You can copy byte by byte using a pointer, as in my example above:

for (int i = 0; i < sizeof(WrkSettings); i++){
  EEPROM.write(i, *((uint8_t*) &WrkSettings + i));
}
[ - ]
Reply by MaxMaxfieldJune 25, 2021

Re memcpy() not working -- that's what I thought (in my own slow "hardware-brained" way :-)

But although I can understand your code, I fear it would be tricky for beginners to wrap their brains around it -- not the least that I haven;t introduced them to the delights (and dangers) of C pointers yet -- by comparison, the concept of the union is something that a bear of little brain like myself can understand.

[ - ]
Reply by tcfkatJune 25, 2021

Hello Vadim,

yes, you're right, WrkSettings = DefSettings is okay also.

If DefSettings is really constant it should be declared as such, but only Max can answer this.

[ - ]
Reply by MaxMaxfieldJune 25, 2021

Most of the values in DefSettings are constant apart from the Checksum value -- speaking of which, I just posted another question (I'm sorry): https://www.embeddedrelated.com/thread/13888/why-crc-with-eprom

Apart from the fact that the values associated with "constant variables" can't be changed under program control, is there any other value to them (e.g., in terms of memory / where they are stored)?

[ - ]
Reply by tcfkatJune 25, 2021

Apart from the fact that the values associated with "constant variables" can't be changed under program control, is there any other value to them (e.g., in terms of memory / where they are stored)?

Initialized vars not declared as const are stored in ROM (read: FLASH, EPROM) and copied by the start-up code into RAM. This is done before the user program is executed, i.e. before main() is called.

If vars are declared const they do not need to be copied into RAM, so this saves ressources.

[ - ]
Reply by MaxMaxfieldJune 25, 2021

Dang -- I wish I'd known that years ago LOL