Initializing a struct in a union (or not, as the case might be)
Started by 3 years ago●21 replies●latest reply 3 years ago●1085 viewsAs 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 ()
{
}
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;
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
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!
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 :-)
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?
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
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
}
};
O-M-G! That works!!! Thank you!!!
You are welcome!
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:
}
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
}
};
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
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.
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
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)); }
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.
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.
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)?
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.
Dang -- I wish I'd known that years ago LOL
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