J-Dizzle
J-Dizzle

Reputation: 5153

GCC Preproccesor macro to determine maximum size of multiple structs

Is there a way to generate a macro which yields the max size of a list of data types?

Goal

typedef struct {
    uint8_t x;
} A;

typedef struct {
    uint16_t x;
} B;

typedef struct {
    uint8_t x[10];
} C;

#define sizeof_max(A,B,C)   //compiles to '10'

Use Case

Different value-mappings for a common data-segment.

typedef union {
    uint8_t array[sizeof_max(A,B,C)];
    A iso_hugenum_a;
    B ext_a0;
    C ext_a1;  //and someday down the road 'D' may also get added
} DATA_SEG;

This is for an embedded application where the device implements a base protocol ISO_HUGENUM_A. The device must also support extensions to this protocol EXT_A0, EXT_A1, EXT_B0. Heck, in this case there is a real chance of EXT_C0 showing up down the road (yuck)!

Notes

The primary goal here is for a top-level system to know the data-segment's size in an extensible and safe way. It is tempting to just say 'cast as array' when you need as array. But

I am hoping there is a solution, but have not found one yet. Any ideas? All the sizes would be generated by the pre-processor, so it seems like an ideal candidate for a macro.

-Justin

Upvotes: 4

Views: 1242

Answers (2)

Clifford
Clifford

Reputation: 93524

The following macro definition does precisely what you have asked:

#define sizeof_max(s1,s2,s3) sizeof( union{s1 a; s2 b; s3 c; })

For your example structures the following:

size_t s = sizeof_max( A,B,C ) ;

results in s = 10.

Of course you could omit the array member and simply cast a DATA_SEG object address to a uint8_t* when you want to access as a byte array:

DATA_SEG x ;
uint8_t* array = (uint8_t*)&x ; 

That would allow DATA_SEG to add more structures if necessary without having to change the macro - a lot safer and more maintainable.


Added:

Another possibility is to separate the specialised interpretations from the byte overlay thus:

typedef union 
{
    A iso_hugenum_a;
    B ext_a0;
    C ext_a1;  
    D added_someday ;
} DATA_SEG_U;

typedef union 
{
    uint8_t array[sizeof(DATA_SEG_U)];
    DATA_SEG_U data_seg ;
} DATA_SEG ;

Upvotes: 6

J-Dizzle
J-Dizzle

Reputation: 5153

Combining variadic macros with multi-level pre-processing can work -

This achieves the desired goal 'for up to 5 inputs'.

#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,N,...) N

#define VA_NUM_ARGS(...)                 VA_NUM_ARGS_IMPL(__VA_ARGS__, 5,4,3,2,1)
#define macro_dispatcher__(func,nargs)   func ## nargs
#define macro_dispatcher_(func, nargs)   macro_dispatcher__(func, nargs)
#define macro_dispatcher(func, ...)      macro_dispatcher_(func, VA_NUM_ARGS(__VA_ARGS__))

#define sizeof_max(...) macro_dispatcher(sizeof_max, __VA_ARGS__)(__VA_ARGS__)

#define sizeof_max1(a)         (sizeof(a))
#define sizeof_max2(a,b)       (sizeof(a)>sizeof(b)?sizeof(a):sizeof(b))
#define sizeof_max3(a,b,c)     sizeof_max2(sizeof_max2(a,b), c)
#define sizeof_max4(a,b,c,d)   sizeof_max2(sizeof_max3(a,b,c),d)
#define sizeof_max5(a,b,c,d,e) sizeof_max2(sizeof_max4(a,b,c,d),e)

This will work for GCC/C99. It is 'a' solution to the problem, and I post here because I learned much from it, and want to share it. That being said, read the disclaimer at end:).

References

The use of macro_dispatcher() was provided by 'rmn' from the efesx forum here:

Adding fcn# to the function name also described here:

Of course, here are some GCC variadic macro pages:

Example

typedef union {
    uint8_t array[sizeof_max(A,B,C)];  //array is of size 10.
    A iso_hugenum_a;
    B ext_a0;
    C ext_a1; 
} DATA_SEG;

Breakdown

you can try this code in 'main.c' and compile with 'gcc -E main.c' to observe the macro-ness:

int main (void) {
    uint8_t rslt;

    //preprocs to 'rslt=C;'
    rslt = VA_NUM_ARGS_IMPL(A,B,C,A,B,C,A);

    //preprocs to 'rslt=3'
    rslt = VA_NUM_ARGS(A,B,C);

    //preprocs to 'rslt=maxN'
    rslt = macro_dispatcher__(max, N);

    //preprocs to 'rslt=macro_dispatcher_(max, N);'
    rslt = macro_dispacther_(max, N);

    //preprocs to 'rslt=max3;'
    rslt = macro_dispatcher(max, X, Y, Z);

    //preprocs to 'sizeof_max1(A)/max2(A,B)/max3(A,B,C)'
    rslt = sizeof_max(A);
    rslt = sizeof_max(A,B);
    rslt = sizeof_max(A,B,C);


    //Breakdown:
    //sizeof_max(A,B,C);
    //macro_dispatcher(sizeof_max, __VA_ARGS__)(__VA_ARGS__)
    //macro_dispatcher_(sizeof_max, 3)(__VA_ARGS__)
    //macro_dispatcher__(sizeof_max,3)(__VA_ARGS__)
    //
    //Result:
    //sizeof_max3(__VA_ARGS__)
    //sizeof_max3(A,B,C)
    rslt = sizeof_max(A,B,C);

    return 0;
}

(Disclaimer) Simpler & Safer always wins. As such I will most likely go with something closest to Clifford's answer. No need for me to play cute tricks that will 'byte' someone else down the road.

Upvotes: 1

Related Questions