I would like to create an array of different structs with different sizes.
The resulting array must be tightly packed with no null values between structs.
The whole thing must be initialised at compile time, so it can reside in the flash of an embedded system.
The result is a tree of USB configuration descriptors, every descriptor packed in immediately after the last to produce a single configuration blob. Suggestions of different approaches to the problem would be welcomed. http://www.beyondlogic.org/usbnutshell/usb5.shtml#ConfigurationDescriptors
struct a {
uint16_t some_field;
};
struct b {
uint32_t another_field;
};
union detail {
struct a a;
struct b b;
};
const union detail configuration[] = {
{ .a = { .some_field = 23 } },
{ .b = { .another_field = 12 } }
};
The above example is a significantly simplified version of my current, failing, attempt. Each element of the array is the size of the largest union member. So every array member is 32 bits and the first entry is padded with zeros.
Current output 1700 0000 0c00 0000
Desired output 1700 0c00 0000
Existing methods to generate this packed output use a giant uint8 array with macros to insert more complex values such as 16 bit numbers. An array of structs more accurately represents the data and provides type safety, if it would work.
I don't need to be able to index or access the data from the array, the blob is shoved in to low level USB routines. Playing with the gcc packed attribute did not change the standard union array behaviour.
Accepting comments from @Basile-Starynkevitch, @Jonathan-Leffler and others that what I was hoping for couldn't be done I reconsidered. What I really required was to precisely control the relative placement of the structures in memory/flash. The placement is done with the linker and I eventually found a solution there.
First, inside the SECTIONS portion of the linker script I created a special block. The only way to ensure order is to create multiple sections and manually order them, cpack0-3 in this instance.
Then the struct variables are slotted in to the special sections. The long handed syntax repetitive can be simplified by #define elements in a real implementation.
So we have a configuration variable, aligned at a 4 byte address for nice access and defined using a struct for type safety. The subsequent portions of the configuration are also defined by structs for safety and placed in memory sequentially. The
aligned(1)
attribute ensures that they are packed in tightly with no empty space.This solves my problem, the configuration definition is done via a struct for all the advantages is provides, the ugliness is hidden by a
#define
and the final configuration is a binary blob of variable length accessed by auint8_t*
pointer. As the pointer increments it moves seamlessly across the different configuration elements.