Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to write a _Static_assert in GCC/GNU C that would verify the layout of bit fields in memory at compile time?

Suppose that I have the following definitions:

#include <stdbool.h>
#include <stdint.h>
#define ASSERT(cond) _Static_assert(cond, #cond)

typedef union {
    struct {
        bool bit0:1;
        bool bit1:1;
        bool bit2:1;
        bool bit3:1;
        bool bit4:1;
        bool bit5:1;
        bool bit6:1;
        bool bit7:1;
    };
    uint8_t bits;
} byte;

ASSERT(sizeof(byte) == sizeof(uint8_t));

Is it possible to write a code, such as

#include <assert.h>
// ...
    assert(((byte) { .bit0 = 1 }).bits == 0b00000001);
    assert(((byte) { .bit1 = 1 }).bits == 0b00000010);
    assert(((byte) { .bit2 = 1 }).bits == 0b00000100);
    assert(((byte) { .bit3 = 1 }).bits == 0b00001000);
    assert(((byte) { .bit4 = 1 }).bits == 0b00010000);
    assert(((byte) { .bit5 = 1 }).bits == 0b00100000);
    assert(((byte) { .bit6 = 1 }).bits == 0b01000000);
    assert(((byte) { .bit7 = 1 }).bits == 0b10000000);
// ...

that would cause a compile-time failure if the above conditions weren't satisfied?

(When I try to place the conditions in the ASSERT macro, the compiler complains that expression in static assertion is not constant, which of course makes perfect sense)

The solution is allowed to use the GNU extensions to the C language.

like image 379
Maciek Godek Avatar asked May 21 '20 08:05

Maciek Godek


People also ask

Does C have Static_assert?

It is available in the C11 version of C. static_assert is used to ensure that a condition is true when the code is compiled.

What is Static_assert?

What is static assertion? Static assertions are a way to check if a condition is true when the code is compiled. If it isn't, the compiler is required to issue an error message and stop the compiling process. The condition that needs to be checked is a constant expression. Performs compile-time assertion checking.


3 Answers

I don't think you can. _Static_assert is required to verify that the argument expression satisfies standard C's requirements for an integer constant expression.

There are ways, which on gcc can sometimes turn a boolean expression that doesn't satisfy those requirements but are compile-time-known to the optimizer into a compile-time error or warning.

E.g., :

#include <assert.h>
#if  __GNUC__ && !__clang__
#define $SassertIfUCan0(X) \
    (__extension__({ /*ellicit a -Wvla-larger-than */ \
         (!__builtin_constant_p(X)) ? 0 : \
        ({ char volatile $SassertIfUCan0_[ (!__builtin_constant_p(X)||(X))  ? 1:-1]; \
         $SassertIfUCan0_[0]=0,0;}); \
        __auto_type $SassertIfUCan0 = X; \
         assert($SassertIfUCan0); \
         0; \
    }))
#endif

int main(int C, char **V)
{
    int x = 0; $SassertIfUCan0(x);
    //these also ellicit compile-time errrors:
    /*$SassertIfUCan0(C-C);*/
    /*$SassertIfUCan0(C*0);*/
}

can turn the nullness of the compile-time known variable x, which isn't technically an integer constant, into a compile/time warning/error ("-Wvla-larger-than").

Unfortunately, the macro doesn't work with every expression and that includes your bitfield-based example.

(I wish compilers had a mechanism for failing compilation if an expression happens to be compile-time known and false.)

So AFAIK, the closest thing you can do is compile-time detect platforms whose ABI is known to guarantee your required bitfield layout:

#if __linux__ && __x86_64__
#elif 0//...
//...
#else
   #error "bitfields not known to be little-endian"
#endif
like image 150
PSkocik Avatar answered Oct 17 '22 02:10

PSkocik


I think, this is an X-Y problem: You are asking about checking the layout of bitfields when you really want to write code that is portable across different implementations of bitfields. So:

  • If you don't try to communicate your bitfield to another machine, or store it in a file where a different machine may read it, just forget about the implementation detail of how the bits are ordered. Just access them via the bitfield names, and be done with it.

  • If you need to communicate the structures containing these bitfields, declare a uint8_t and the appropriate set of bit flag constants (#define BIT7 (1u << 7), etc.). Bytes never change value when they are transferred from one machine to another, so myFlags & BIT7 is guaranteed to yield the same result everywhere.

    Note that it is important to either use a single byte to store the flags, or handle the problem of endianess explicitly.

like image 32
cmaster - reinstate monica Avatar answered Oct 17 '22 01:10

cmaster - reinstate monica


On GNU Linux, you might find <features.h> and /usr/x86_64-linux-gnu/include/linux/byteorder/big_endian.h

The solution is allowed to use the GNU extensions to the C language.

With most recent GCC compilers, you could provide your own GCC plugin defining your __your_builtin_endian__ compiler builtin. Notice that some GCC compilers are built without plugin support (e.g. RedHat did that). Check by running gcc -v alone.

Once your plugin defines __your_builtin_endian__, you could use that in static_assert. Or have your plugin define and implement some #pragma MYPLUGIN check endian which would make a compile-time error in some cases.

Do budget a few weeks of fulltime work for such a plugin. It is GCC version specific (not always the same C++ code for a GCC 9 and GCC 10 plugin).

Consider also using autoconf (at least if you do not need any cross-compilation).

like image 2
Basile Starynkevitch Avatar answered Oct 17 '22 01:10

Basile Starynkevitch