3

_Static_assert is built-in to some C compilers such as gcc and clang, but it may not be included in all C compilers. I would like to use the _Static_assert functionality while keeping my code as cross-platform as possible. I figured the best way to do this was to test

#ifdef _Static_assert
    _Static_assert(0, "Test");
#endif

but that doesn't seem to work out. It compiles, but it does not detect that _Static_assert is defined. Then I figured I could test if the compiler was GCC or not, but I read that having __GNUC__ defined doesn't necessarily prove that the compiler used is GCC. This also doesn't detect other compilers where _Static_assert is defined that I may not know about. So my question is, what is the best way to detect if the compiler supports _Static_assert in the preprocessor?

EDIT: This is the solution I came up with that suits my purposes. Thanks to @KamilCuk below for the link that helped me out.

// Check that we can use built-in _Static_assert
#if defined( __STDC_VERSION__ ) && __STDC_VERSION__ >= 201112L
    #define WE_HAVE_STATIC_ASSERT 1
#endif

#if WE_HAVE_STATIC_ASSERT
    _Static_assert(0, "Test");
#endif

This code works for me on both gcc and clang: https://godbolt.org/z/svaYjWj4j

FINAL EDIT (I think): This provides an answer to my original question about how to detect if _Static_assert is available. It also provides a fallback option that results in relatively helpful errors in most compilers I tested.

Here is the link to the test code: https://godbolt.org/z/TYEj7Tezd

    // Check if we can use built-in _Static_assert
    #if defined( __STDC_VERSION__ ) && __STDC_VERSION__ >= 201112L
        #define MY_STATIC_ASSERT(cond, msg) _Static_assert(cond, msg)
    
    #else // We make our own
        // MY_JOIN macro trick generates a unique token
        #define MY_JOIN2(pre, post) MY_JOIN3(pre, post)
        #define MY_JOIN3(pre, post) pre ## post
    
        #if defined( __COUNTER__ ) // try to do it the smart way...
            #define MY_JOIN(pre) MY_JOIN2(pre, __COUNTER__)
            #define MY_STATIC_ASSERT(cond, msg) \
            static const char *MY_JOIN(static_assert)[(cond) * 2 - 1] = { msg }
    
        #else // we did our best... 
        //will break if static assert on same line in different file
            #define MY_JOIN(pre) MY_JOIN2(pre, __LINE__)
            #define MY_STATIC_ASSERT(cond, msg) \
            static const char *MY_JOIN(static_assert)[(cond) * 2 - 1] = { msg }
        #endif
    #endif
    
    /* - CHANGE CODE HERE TO TEST THE ASSERTIONS - */
    enum {
        A = 3,
        B = 3,
        C = B - A
    };
    /* - --------------------------------------- - */
    
    // Test to see if the enum values match our assertions
    MY_STATIC_ASSERT(B > A, "B must be greater than A");
    MY_STATIC_ASSERT(C > 0, "C must be greater than zero");

Helpful information I used to make this came from these links:

http://jonjagger.blogspot.com/2017/07/compile-time-assertions-in-c.html

https://www.tutorialspoint.com/cprogramming/c_preprocessors.htm

https://stackoverflow.com/a/43990067/16292858

pa-mims
  • 41
  • 4
  • Testing for `__STDC_VERSION__ >= 201112L` doesn't work if you compile using `-std=c99`. Since `_Static_assert` is a keyword it is available even if the `static_assert` macro is not – eradman Jul 02 '21 at 07:49

1 Answers1

6

How do I test if _Static_assert is defined?

_Static_assert is part of C11. So check for C11.

#if __STDC_VERSION__ > 201112L

You could also #include <assert.h> and check for #ifdef static_assert.

My first google hit for static_assert.h github has a nice example how to handle different tools and compilers: https://github.com/wc-duck/dbgtools/blob/master/include/dbgtools/static_assert.h#L68 .


If you want to write a C11 compatibility layer and use static assertion in your code, for example use this answer and fallback to your own wrapper:

// static_assert.h
#define CTASTR2(pre,post) pre ## post
#define CTASTR(pre,post) CTASTR2(pre,post)
#define STATIC_ASSERT(cond) \
    typedef struct { int static_assertion_failed : !!(cond); } \
        CTASTR(static_assertion_failed_,__COUNTER__)

#include <assert.h>
#ifndef static_assert
#define static_assert(expr, str)  STATIC_ASSERT(expr)
#endif

// somefile.c
#include <static_assert.h>
static_assert(something == something, "Uwu");
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • Thanks for the tip. I can't use the macro version because I'm asserting that the values defined in an enum make sense. If I am not mistaken, enums are parsed and evaluated after the preprocessor runs. But being able to test that the built-in compiler version exists is very helpful. I can definitely make use of that link. I appreciate your time. – pa-mims Jun 30 '21 at 07:57
  • 2
    I do not understand you comment. I do not see a link from "checking if _Static_assert is supported" to "asserting that some enum values makes sense". – KamilCuk Jun 30 '21 at 08:00
  • for example: `enum some_constants { A = 5, B = 3, C = A - B }; _Static_assert(B > 0, "B must be greater than zero"); _Static_assert(A > B, "A must be greater than B");` In this case, I have some constant values that must be evaluated as constant expressions. Perhaps I need to use these values to define the size of some arrays for example. I need to ensure that C remains positive while its value is based on both A and B. If I am not mistaken, I can't use a macro to assert these values at compile time. Built-in _Static_assert() works for this case. – pa-mims Jun 30 '21 at 13:56
  • `If I am not mistaken, I can't use a macro to assert these values at compile time` https://stackoverflow.com/questions/3385515/static-assert-in-c whole thread with endless examples. The example I linked also seems to have a its own static assert macro.... I also showed you what you can research on google - I linked to the first hit, there are many, many more example on github, research it. – KamilCuk Jun 30 '21 at 14:15
  • 1
    @pa-mims: It's true that you can't evaluate `enum` constants in the preprocessor pass, but that isn't what the "fallback" code is doing. The macro expands the `static_assert` to code that will be a constraint violation (a zero-width bitfield with a declarator) if the asserted expression evaluates to zero. The constraint is checked in the compiler pass, not in the preprocessor, so the value of the `enum` constant is available to it. You don't get a nice message but you are guaranteed to get a diagnostic. Therefore I suspect that you can use it after all. – Nate Eldredge Jun 30 '21 at 16:19
  • @pa-mims: Indeed, you can see here that it works with your example: https://godbolt.org/z/13975br46 – Nate Eldredge Jun 30 '21 at 16:24
  • @Nate Eldridge: Interesting. I will look into that. Thank you. I also discovered that the clang specific part of my code above is unnecessary. clang apparently also defines \_\_STDC_VERSION\_\_ https://godbolt.org/z/Me516hc91 – pa-mims Jun 30 '21 at 16:34
  • 1
    `Seems like you don't understand for some reason that I can't use a macro-defined static assert for my case` Exactly, I do not understand that and I do not understand the reason. You can use macro defined static assertion to statically check enum values. – KamilCuk Jun 30 '21 at 16:50
  • @KamilCuk Nate Eldridge above explained what I meant. enum constants can't be evaluated in the preprocessor pass. With that said, I admit that the macro you showed me works because it will create a zero-width bitfield which will cause the compiler to error. I am incorporating that into my solution code now to fall back on if _Static_assert is not built-in to the compiler. Thank you. – pa-mims Jun 30 '21 at 16:56
  • @Nate Eldredge: Sorry, I’m not sure where you’re talking about, but yes, `__STDC_VERSION__` is what I needed to check. I think I have a pretty decent solution in my most recent edit now. – pa-mims Jul 01 '21 at 02:05