Reputation: 567
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.
Upvotes: 0
Views: 538
Reputation: 222650
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;
};
};
Upvotes: 5
Reputation: 21
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.
Upvotes: 2