Consider the struct:
struct MainStruct {
struct A a;
struct B b;
struct C c;
};
Normally, a compiler is expected to place a, b and c in memory successively with some alignment.
But what if I would like to make variables of a, b and c always start at an exact offset inside mainStruct independent of their sizes? What is a convenient way to do that 'at compile time'?
As an example: mainStruct may be 256 bytes in total, while a always starts at &mainStruct
, b always starts at &mainstruct 100
and c always starts at &mainStruct 179
.
EDIT:
The address of mainStruct may be variable. The important point here is that the offsets are always constant. Whenever I define a variable of type Struct MainStruct
, that variable should always have the same memory layout with inner structs having constant offsets.
CodePudding user response:
This can be done with anonymous unions and compiler features to pack structures and unions. Since #pragma pack(1)
appears to work with MSVC, GCC, and Clang, here is a simple solution with it:
#include <stddef.h> // Included for offsetof macro.
#include <stdio.h>
struct A { char c; int whatever; };
struct B { char c; int whatever; };
struct C { char c; int whatever; };
#define MemberBOffset 100
#define MemberCOffset 179
#pragma pack(push, 1)
union MainStruct
{
struct
{
// No padding.
struct A a;
};
struct
{
char paddingB[MemberBOffset];
struct B b;
};
struct
{
char paddingC[MemberCOffset];
struct C c;
};
};
#pragma pack(pop)
int main(void)
{
printf("Offset of member a = %td.\n", offsetof(union MainStruct, a));
printf("Offset of member b = %td.\n", offsetof(union MainStruct, b));
printf("Offset of member c = %td.\n", offsetof(union MainStruct, c));
}
Note this improves on the solution in Henry Gilbert’s answer by using fewer structure/union nestings and by using anonymous structures so the members can be accessed in the natural way, such as foo.b
instead of foo.memberData.b
.
If struct MainStruct
is desired instead of union MainStruct
, the union can be made anonymous and nested inside a struct MainStruct
. Alternatively, a typedef
can be used.
The above expects the offset of member a
is zero and the other offsets are not zero. If that is not always the case, we can embellish:
#define MemberAOffset 0
#define MemberBOffset 100
#define MemberCOffset 179
#pragma pack(push, 1)
union MainStruct
{
struct
{
#if 0 < MemberAOffset
char paddingA[MemberAOffset];
#endif
struct A a;
};
struct
{
#if 0 < MemberBOffset
char paddingB[MemberBOffset];
#endif
struct B b;
};
struct
{
#if 0 < MemberCOffset
char paddingC[MemberCOffset];
#endif
struct C c;
};
};
#pragma pack(pop)
The #pragma pack
directive has gross effect; it would affect any structures declared inside union MainStruct
. That is, if instead of struct A a;
, we had struct A { char c; int i; } a;
, it would pack struct A
instead of using its natural padding. Declaring struct A
, struct B
, and struct C
prior to the directive avoids this. However, an alternative with GCC and Clang is to use the finer __attribute__
notation without a #pragma pack
directive.
union __attribute__((__packed__)) MainStruct
{
struct __attribute__((__packed__))
{
#if 0 < MemberAOffset
char paddingA[MemberAOffset];
#endif
struct A a;
};
struct __attribute__((__packed__))
{
#if 0 < MemberBOffset
char paddingB[MemberBOffset];
#endif
struct B b;
};
struct __attribute__((__packed__))
{
#if 0 < MemberCOffset
char paddingC[MemberCOffset];
#endif
struct C c;
};
};
CodePudding user response:
Looks like this solution requires very platform specific manipulation based on the platform's ability to pack structs. Here is my solution using Visual Studio 2022.
// These defines can be done using relative offsets. These below are absolute offsets and require subtraction, as seen below.
#define MAIN_STRUCT_SIZE_BYTES 256
#define MEMBER_B_OFFSET_FROM_START 100
#define MEMBER_C_OFFSET_FROM_START 179
#pragma pack(1)
union
{
uint8_t offset[MAIN_STRUCT_SIZE_BYTES];
struct
{
union
{
uint8_t offset[MEMBER_B_OFFSET_FROM_START ]; // Fix the size to define the next member's offset
struct
{
int32_t exampleData1;
int32_t exampleData2;
} memberAData;
} memberA;
union
{
uint8_t offset[MEMBER_C_OFFSET_FROM_START- MEMBER_B_OFFSET_FROM_START]; // Fix the size to define the next member's offset
struct
{
int32_t exampleData1;
int32_t exampleData2;
} memberBData;
} memberB;
union
{
uint8_t offsetC[MAIN_STRUCT_SIZE_BYTES - MEMBER_C_OFFSET_FROM_START];
struct
{
int32_t exampleData1;
int32_t exampleData2;
} memberCData;
} memberC;
} memberData;
} mainStruct ;
Outside of this application, you'll have to manually maintain the offset values. Here is the test script and output:
int main(void) {
printf("Mainstruct size in bytes: %llu \n", sizeof(mainStruct));
printf("Address of mainStruct: 0x%x \n", &mainStruct);
printf("Address of member A: 0x%x \n", & (mainStruct.memberData.memberA));
printf("Address of member B: 0x%x \n", &(mainStruct.memberData.memberB));
printf("Address of member C: 0x%x \n", &(mainStruct.memberData.memberC));
printf("Size of member A: %llu \n", sizeof(mainStruct.memberData.memberA));
printf("Size of member B: %llu \n", sizeof(mainStruct.memberData.memberB));
printf("Size of member C: %llu \n", sizeof(mainStruct.memberData.memberC));
}
With the output as follows:
Mainstruct size in bytes: 256
Address of mainStruct: 0x8e0ac180
Address of member A: 0x8e0ac180
Address of member B: 0x8e0ac1e4
Address of member C: 0x8e0ac233
Size of member A: 100
Size of member B: 79
Size of member C: 77
Here, the address of member A equals the start address of mainStruct. Member B's address is 100 bytes greater than member A, and member C's start address is 179 bytes greater than the start of mainStruct.