12

Is there any possible way to make the compiler bail out if the sizeof (struct Astruct) is uneven?

Background information: We have a 16-bit microprocessor which will give processor alignment errors if a 16-bit value is mis-aligned. That might happen in the following scenario:

typedef struct
{
    U8BIT u8BitValue1;
    U8BIT u8BitValue2;
    U8BIT u8BitValue3;
} unevenAmountOf8BitValues;

typedef struct
{
    U16BIT u16BitValue1;
    U16BIT u16BitValue2;
} my16BitValues;

#define U8BIT_COUNT 3
#define U16BIT_COUNT 2

typedef struct
{
    unevenAmountOf8BitValues u8BitValues;
    my16BitValues u16BitValues;
} valuesCombined;

typedef union
{
    valuesCombined myValues;
    U8BIT buffer[sizeof(valuesCombined)];

    struct
    {
         U8BIT bufferU8[U8BIT_COUNT];
         U16BIT bufferU16[U16BIT_COUNT]; /* <<-- missalignment */
    } valuesPerType;
} myValuesInRamAndRom

What we do now is counting the amount of U8BIT/U16BIT/U32BIT values (well, keeping track of the amount using excel) manually and putting that in the U(8/16/32)BIT_COUNT define and then the following:

#if U8BIT_COUNT % 2 == 1
#error The number of U8BIT parameters need to be even, add a dummy
#endif

Keeping track of the amount of U8-/U16-/U32BIT values is pretty error prone and we've had quite some moments that we were thinking "hey, it ain't working", an hour or what later, oh! Darn, forgot to adjust the amount of values define.

A preferred method would be to use the sizeof operator, however that can't be used in the error checking, which I would really like to keep.

So is there anyway to use the sizeof operator and to keep some form of error checking that the amount of U8BIT values must be even?


Combined solution by Lundin and Aaron McDaid:

#define COMPILE_TIME_ASSERT(expr) {typedef U8BIT COMP_TIME_ASSERT[((!!(expr))*2-1)];}
Community
  • 1
  • 1
Daan Timmer
  • 14,771
  • 6
  • 34
  • 66
  • if the misalignment always occurs in a single structure, write a separate perl/python/bash(?) script which will check it and place it in your `Makefile`. – Dariusz Oct 16 '13 at 12:24
  • @Dariusz Might be handy to note, we are using Fujitsu Softune compiler, so no handy dandy `Makefile` mumbo-jumbo. To be more precise: FFMC-16 Family Softune Workbench V30L29 – Daan Timmer Oct 16 '13 at 12:28
  • @Dariusz I prefer to not use external tools like perl/python/bash because I am the only one in this company that knows anything else but C (Python, yay!). And I am an external person and will be leaving here in a month or two. So I prefer something that works within C, -if possible. – Daan Timmer Oct 16 '13 at 12:33
  • @DaanTimmer And how about C++? Can you use that? `static_assert` could be used then. –  Oct 16 '13 at 12:36
  • @H2CO3 Which is also possible in C, since C11. – Lundin Oct 16 '13 at 13:03
  • @Lundin But few compilers actually support C11. C++11 is more widely implemented. –  Oct 16 '13 at 13:08

3 Answers3

11

With a C11 compiler, use:

static_assert (sizeof(the struct) % 2 == 0,
               "Misaligned");

With older compilers, you can use dirty tricks like

#define COMPILE_TIME_ASSERT(expr) typedef char COMP_TIME_ASSERT[(expr) ? 1 : 0];

...

COMPILE_TIME_ASSERT(sizeof(the_struct) % 2 == 0);

The real solution to your specific problem might however be to ensure that struct padding is enabled. You shouldn't get any misalignments then.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Sadly no C11 compiler. See comment: http://stackoverflow.com/questions/19403233/compile-time-struct-size-check-error-out-if-odd#comment28760129_19403233 – Daan Timmer Oct 16 '13 at 12:48
  • @DaanTimmer Then use the COMPILE_TIME_ASSERT. It gives an "array can't be declared with size zero" compiler error each time the struct has an odd size. – Lundin Oct 16 '13 at 12:50
  • Yeah I read too fast and didn't see the second part of your answer. I'll throw both answers in the group to see which one will be the preferred method. – Daan Timmer Oct 16 '13 at 12:52
  • @DaanTimmer If static_assert isn't an option, I'd say the typedef. Because you can easily alter the macro and put the compile-time check outside a function if you want. Simply remove the { }. But then I suppose you could also make a function which is never called from anywhere, and put all static checks inside it. – Lundin Oct 16 '13 at 12:56
  • yours does not give any compile errors, only 1 (even) or 2 (odd) warnings are raised. – Daan Timmer Oct 16 '13 at 13:07
  • That is: `W1207C: conditional expression is constant: operator :'`. I've changed `1 : 0` to `1 : -1` which does raise an error. – Daan Timmer Oct 16 '13 at 13:19
  • I've combined your answer together with Aaron McDaid's comment on Zeta's answer and made the following: `#define COMPILE_TIME_ASSERT(expr) typedef U8BIT COMP_TIME_ASSERT[((!!(expr))*2-1)];` This now does not raise a warning about the constant operator `?:` and solves my problem. – Daan Timmer Oct 16 '13 at 13:28
  • @DaanTimmer Which compiler gives a warning for that? Seem like a rather superfluous warning. I think you can safely disable that one. – Lundin Oct 16 '13 at 13:44
9

It's possible, using a trick that's also being used in the Linux kernel:

#define BUILD_BUG_OR_ZERO(e) (sizeof(struct{ int:-!!(e);}))
#define ENSURE_EVEN_SIZE(e) BUILD_BUG_OR_ZERO(sizeof(e) % 2 == 1)

struct uneven{
  char a,b,c;
};

struct even{
  char a,b,c,d;
};

int main(){
  ENSURE_EVEN_SIZE(struct even);
  /* compiler error: */
  ENSURE_EVEN_SIZE(struct uneven);
}

If sizeof(e) % 2 == 1 is true, the bitfield int:-!!(e) would have a negative size, which is forbidden. (Ideone)

Community
  • 1
  • 1
Zeta
  • 103,620
  • 13
  • 194
  • 236
  • 2
    However, the rationale behind that Linux kernel macro is pretty obscure iirc. They could have declared a zero sized array instead, but since the Linux kernel relies on non-standard GCC features like flexible array members declared with size 0, they couldn't do that. If you are using standard C, simply typedef an array with length zero instead. Far more sane and readable. – Lundin Oct 16 '13 at 12:46
  • (In other words, the Linux kernel is using the obscure, pre-C99, non-standard feature from GCC which enables you to write: `struct a {int x; int arr[0]; }`. That's why they wrote an obscure zero-size bit field version instead of declaring a zero-length array.) – Lundin Oct 16 '13 at 13:01
  • We could arrange for the size of the array to be 1 or -1, depending on true or false. This would avoid any confusion about the validity of zero-length arrays. `#define BUILD_BUG_OR_ZERO(e) (sizeof(struct{ char x[ (!!(e))*2-1 ]; }))` – Aaron McDaid Oct 16 '13 at 13:03
  • Sadly, this answer will not compile it either errors out on "bitfield negative" or "bitfield zero". Along with that it raises 2 extra warnings and we sadly have to treat warnings as errors. – Daan Timmer Oct 16 '13 at 13:04
  • @Lundin: I remember [your opinion on this macro](http://stackoverflow.com/questions/9229601/what-is-in-c-code#comment11624147_9229601). I would prefer `static_assert`, but I don't find the bit field version that obscure. – Zeta Oct 16 '13 at 13:05
  • 1
    If using this bitfield technique, what about `((!!(e))*2-1)` instead of `-!!(e)`? This would be equal to -1 or 1, avoid the issue with zero-width bitfield. – Aaron McDaid Oct 16 '13 at 13:07
5

Here is the version which allows using same assertion macro multiple times in the same file.

/*
    General purpose static assert.

    Works in/out -side of scope:
        STATIC_ASSERT(sizeof(long)==8);
        int main()
        {
            STATIC_ASSERT(sizeof(int)==4);
        }
*/
#define STATIC_ASSERT(X)            STATIC_ASSERT2(X,__LINE__)

/*
    These macros are required by STATIC_ASSERT to make token pasting work.
    Not really useful by themselves.
*/
#define STATIC_ASSERT2(X,L)         STATIC_ASSERT3(X,L)
#define STATIC_ASSERT3(X,L)         STATIC_ASSERT_MSG(X,at_line_##L)

/*
    Static assertion with special error message.
    Note: It depends on compiler whether message is visible or not!

    STATIC_ASSERT_MSG(sizeof(long)==8, long_is_not_eight_bytes);
*/
#define STATIC_ASSERT_MSG(COND,MSG) \
    typedef char static_assertion_##MSG[(!!(COND))*2-1]
user694733
  • 15,208
  • 2
  • 42
  • 68