Home > Blockchain >  How to assign a specific offset to each member inside a single struct?
How to assign a specific offset to each member inside a single struct?

Time:12-08

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.

  • Related