Static assert in C

前端 未结 12 616
感情败类
感情败类 2020-11-22 16:22

What\'s the best way to achieve compile time static asserts in C (not C++), with particular emphasis on GCC?

相关标签:
12条回答
  • 2020-11-22 16:49

    For those of you wanting something really basic and portable but don't have access to C++11 features, I've written just the thing.
    Use STATIC_ASSERT normally (you can write it twice in the same function if you want) and use GLOBAL_STATIC_ASSERT outside of functions with a unique phrase as the first parameter.

    #if defined(static_assert)
    #   define STATIC_ASSERT static_assert
    #   define GLOBAL_STATIC_ASSERT(a, b, c) static_assert(b, c)
    #else
    #   define STATIC_ASSERT(pred, explanation); {char assert[1/(pred)];(void)assert;}
    #   define GLOBAL_STATIC_ASSERT(unique, pred, explanation); namespace ASSERTATION {char unique[1/(pred)];}
    #endif
    
    GLOBAL_STATIC_ASSERT(first, 1, "Hi");
    GLOBAL_STATIC_ASSERT(second, 1, "Hi");
    
    int main(int c, char** v) {
        (void)c; (void)v;
        STATIC_ASSERT(1 > 0, "yo");
        STATIC_ASSERT(1 > 0, "yo");
    //    STATIC_ASSERT(1 > 2, "yo"); //would compile until you uncomment this one
        return 0;
    }
    

    Explanation:
    First it checks if you have the real assert, which you would definitely want to be using if it's available.
    If you don't it asserts by getting your predicate, and dividing it by itself. This does two things.
    If it's zero, id est, the assertion has failed, it will cause a divide by zero error (the arithmetic is forced because it is trying to declare an array).
    If it is not zero, it normalises the array size to 1. So if the assertion passed, you wouldn't want it to fail anyway because your predicate evaluated to -1 (invalid), or be 232442 (massive waste of space, IDK if it would be optimised out).
    For STATIC_ASSERT it is wrapped in braces, this makes it a block, which scopes the variable assert, meaning you can write it many times.
    It also casts it to void, which is a known way to get rid of unused variable warnings.
    For GLOBAL_STATIC_ASSERT, instead of being in a code block, it generates a namespace. Namespaces are allowed outside of functions. A unique identifier is required to stop any conflicting definitions if you use this one more than once.


    Worked for me on GCC and VS'12 C++

    0 讨论(0)
  • 2020-11-22 16:51

    This works in function and non-function scope (but not inside structs,unions).

    #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
    
    STATIC_ASSERT(1,this_should_be_true); 
    
    int main()
    {
     STATIC_ASSERT(1,this_should_be_true); 
    }
    
    1. If the compile time assertion could not be matched, then an almost intelligible message is generated by GCC sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative

    2. The macro could or should be changed to generate a unique name for the typedef (i.e. concatenate __LINE__ at the end of the static_assert_... name)

    3. Instead of a ternary, this could be used as well #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1] which happens to work even on the rusty olde cc65 (for the 6502 cpu) compiler.

    UPDATE: For completeness sake, here's the version with __LINE__

    #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(!!(COND))*2-1]
    // token pasting madness:
    #define COMPILE_TIME_ASSERT3(X,L) STATIC_ASSERT(X,static_assertion_at_line_##L)
    #define COMPILE_TIME_ASSERT2(X,L) COMPILE_TIME_ASSERT3(X,L)
    #define COMPILE_TIME_ASSERT(X)    COMPILE_TIME_ASSERT2(X,__LINE__)
    
    COMPILE_TIME_ASSERT(sizeof(long)==8); 
    int main()
    {
        COMPILE_TIME_ASSERT(sizeof(int)==4); 
    }
    

    UPDATE2: GCC specific code

    GCC 4.3 (I guess) introduced the "error" and "warning" function attributes. If a call to a function with that attribute could not be eliminated through dead code elimination (or other measures) then an error or warning is generated. This can be used to make compile time asserts with user defined failure descriptions. It remains to determine how they can be used in namespace scope without resorting to a dummy function:

    #define CTC(X) ({ extern int __attribute__((error("assertion failure: '" #X "' not true"))) compile_time_check(); ((X)?0:compile_time_check()),0; })
    
    // never to be called.    
    static void my_constraints()
    {
    CTC(sizeof(long)==8); 
    CTC(sizeof(int)==4); 
    }
    
    int main()
    {
    }
    

    And this is how it looks like:

    $ gcc-mp-4.5 -m32 sas.c 
    sas.c: In function 'myc':
    sas.c:7:1: error: call to 'compile_time_check' declared with attribute error: assertion failure: `sizeof(int)==4` not true
    
    0 讨论(0)
  • 2020-11-22 16:51

    cl

    I know the question explicitly mentions gcc, but just for completeness here is a tweak for Microsoft compilers.

    Using the negatively sized array typedef does not persuade cl to spit out a decent error. It just says error C2118: negative subscript. A zero-width bitfield fares better in this respect. Since this involves typedeffing a struct, we really need to use unique type names. __LINE__ does not cut the mustard — it is possible to have a COMPILE_TIME_ASSERT() on the same line in a header and a source file, and your compile will break. __COUNTER__ comes to the rescue (and it has been in gcc since 4.3).

    #define CTASTR2(pre,post) pre ## post
    #define CTASTR(pre,post) CTASTR2(pre,post)
    #define STATIC_ASSERT(cond,msg) \
        typedef struct { int CTASTR(static_assertion_failed_,msg) : !!(cond); } \
            CTASTR(static_assertion_failed_,__COUNTER__)
    

    Now

    STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
    

    under cl gives:

    error C2149: 'static_assertion_failed_use_another_compiler_luke' : named bit field cannot have zero width

    Gcc also gives an intelligible message:

    error: zero width for bit-field ‘static_assertion_failed_use_another_compiler_luke’

    0 讨论(0)
  • 2020-11-22 16:57

    C11 standard adds the _Static_assert keyword.

    This is implemented since gcc-4.6:

    _Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
    

    The first slot needs to be an integral constant expression. The second slot is a constant string literal which can be long (_Static_assert(0, L"assertion of doom!")).

    I should note that this is also implemented in recent versions of clang.

    0 讨论(0)
  • 2020-11-22 16:59

    Because:

    1. _Static_assert() is now defined in gcc for all versions of C, and
    2. static_assert() is defined in C++11 and later

    The following simple macro for STATIC_ASSERT() therefore works in:

    1. C++:
      1. C++11 (g++ -std=c++11) or later
    2. C:
      1. gcc -std=c90
      2. gcc -std=c99
      3. gcc -std=c11
      4. gcc (no std specified)

    Define STATIC_ASSERT as follows:

    /* For C++: */
    #ifdef __cplusplus
        #ifndef _Static_assert
            #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
        #endif
    #endif
    /* Now for gcc (C) (and C++, given the define above): */
    #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
    

    Now use it:

    STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed" 
    

    Examples:

    Tested in Ubuntu using gcc 4.8.4:

    Example 1: good gcc output (ie: the STATIC_ASSERT() codes works, but the condition was false, causing a compile-time assert):

    $ gcc -Wall -o static_assert static_assert.c && ./static_assert
    static_assert.c: In function ‘main’
    static_assert.c:78:38: error: static assertion failed: "(1 > 2) failed"
    #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
    ^
    static_assert.c:88:5: note: in expansion of macro ‘STATIC_ASSERT’
    STATIC_ASSERT(1 > 2);
    ^

    Example 2: good g++ -std=c++11 output (ie: the STATIC_ASSERT() codes works, but the condition was false, causing a compile-time assert):

    $ g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert
    static_assert.c: In function ‘int main()’
    static_assert.c:74:32: error: static assertion failed: (1 > 2) failed
    #define _Static_assert static_assert /* static_assert is part of C++11 or later */
    ^
    static_assert.c:78:38: note: in expansion of macro ‘_Static_assert’
    #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
    ^
    static_assert.c:88:5: note: in expansion of macro ‘STATIC_ASSERT’
    STATIC_ASSERT(1 > 2);
    ^

    Example 3: failed C++ output (ie: the assert code doesn't work properly at all, since this is using a version of C++ before C++11):

    $ g++ -Wall -o static_assert static_assert.c && ./static_assert
    static_assert.c:88:5: warning: identifier ‘static_assert’ is a keyword in C++11 [-Wc++0x-compat]
    STATIC_ASSERT(1 > 2);
    ^
    static_assert.c: In function ‘int main()’
    static_assert.c:78:99: error: ‘static_assert’ was not declared in this scope
    #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
    ^
    static_assert.c:88:5: note: in expansion of macro ‘STATIC_ASSERT’
    STATIC_ASSERT(1 > 2);
    ^

    Full test results here:

    /*
    static_assert.c
    - test static asserts in C and C++ using gcc compiler
    
    Gabriel Staples
    4 Mar. 2019 
    
    To be posted in:
    1. https://stackoverflow.com/questions/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756
    2. https://stackoverflow.com/questions/3385515/static-assert-in-c/7287341#7287341
    
    To compile & run:
      C:
        gcc -Wall -o static_assert static_assert.c && ./static_assert
        gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert
        gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert
        gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert
      C++:
        g++ -Wall -o static_assert static_assert.c && ./static_assert
        g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert
        g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert
        g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert
    
    -------------
    TEST RESULTS:
    -------------
    
    1. `_Static_assert(false, "1. that was false");` works in:
      C:
        gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
        gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
        gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
        gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
      C++:
        g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
        g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
        g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
        g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  NO
    
    2. `static_assert(false, "2. that was false");` works in:
      C:
        gcc -Wall -o static_assert static_assert.c && ./static_assert             NO
        gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    NO
        gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    NO
        gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    NO
      C++:
        g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
        g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
        g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
        g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES
    
    3. `STATIC_ASSERT(1 > 2);` works in:
      C:
        gcc -Wall -o static_assert static_assert.c && ./static_assert             YES
        gcc -Wall -std=c90 -o static_assert static_assert.c && ./static_assert    YES
        gcc -Wall -std=c99 -o static_assert static_assert.c && ./static_assert    YES
        gcc -Wall -std=c11 -o static_assert static_assert.c && ./static_assert    YES
      C++:
        g++ -Wall -o static_assert static_assert.c && ./static_assert             NO
        g++ -Wall -std=c++98 -o static_assert static_assert.c && ./static_assert  NO
        g++ -Wall -std=c++03 -o static_assert static_assert.c && ./static_assert  NO
        g++ -Wall -std=c++11 -o static_assert static_assert.c && ./static_assert  YES
    
    */
    
    #include <stdio.h>
    #include <stdbool.h>
    
    /* For C++: */
    #ifdef __cplusplus
        #ifndef _Static_assert
            #define _Static_assert static_assert /* `static_assert` is part of C++11 or later */
        #endif
    #endif
    /* Now for gcc (C) (and C++, given the define above): */
    #define STATIC_ASSERT(test_for_true) _Static_assert((test_for_true), "(" #test_for_true ") failed")
    
    
    int main(void)
    {
        printf("Hello World\n");
    
        /*_Static_assert(false, "1. that was false");*/
        /*static_assert(false, "2. that was false");*/
    
        STATIC_ASSERT(1 > 2);
    
        return 0;
    }
    

    Related:

    1. Use static_assert to check types passed to macro [my own answer]
      1. https://en.cppreference.com/w/cpp/types/is_same
      2. https://en.cppreference.com/w/cpp/language/decltype
    2. Use static_assert to check types passed to macro
    3. How to use static assert in C to check the types of parameters passed to a macro
    0 讨论(0)
  • 2020-11-22 16:59

    This worked for some old gcc. Sorry that I forgot what version it was:

    #define _cat(x, y) x##y
    
    #define _sassert(exp, ln)\
    extern char _cat(SASSERT_, ln)[1]; \
    extern char _cat(SASSERT_, ln)[exp ? 1 : 2]
    
    #define sassert(exp) _sassert((exp), __LINE__)
    
    //
    sassert(1 == 2);
    
    //
    #148 declaration is incompatible with "char SASSERT_134[1]" (declared at line 134)  main.c  /test/source/controller line 134    C/C++ Problem
    
    0 讨论(0)
提交回复
热议问题