Tuesday, January 10, 2012

C11 - Static Assertions

C11 provides the new _Static_assert declaration which allows the use of compile-time assertions. This post describes this new feature and the practical benefits.


C has had the #error preprocessing directive since C89 which can be used to provide error messages based on tests performed by the preprocessor:

#if __STDC__ != 1
#  error "Not a standard compliant compiler"
#endif

Such preprocessor tests have limitations though, most notably the inability to recognize the sizeof operator. This is due to the fact that the preprocessor tokens aren't converted to regular tokens until after preprocessor directives have executed. Because of this, the following won't work:

#if sizeof(long) < 8
#  error "long must be at least 64-bits"
#endif

What is needed is a compile-time assertion mechanism that happens after the preprocessor. _Static_assert provides a way to perform these tests at compile-time. A _Static_assert declaration consists of an integer constant expression and a string literal. If the integer expression evaluates to 0, a diagnostic is produced containing the provided string. For example:

_Static_assert(sizeof(size_t) >= 8, "size_t must be at least 64-bits");
_Static_assert(sizeof(void *) == sizeof(void(*)()), "object pointer must be the same size as function pointer");

_Static_assert can be used to replace #error:

#if __STDC__ != 1
  _Static_assert(0, "Not a standard compliant compiler");
#endif

_Static_assert(FOO_VERSION >= 2, "foo version 2+ required");

Static assertions can also be used in combination with the new _Alignof operator and generic selection mechanism to enforce alignment and type constraints at compile-time:

_Static_assert(_Alignof(char) == 1, "alignment of char must be 1");
_Static_assert(_Generic((int16_t){0}, short:1, default:0), "short int not compatible with int16_t");

C++11 adds static assertions with the same semantics but spells it static_assert instead. Including <assert.h> exposes the static_assert macro that expands to _Static_assert. Static assertions are of even greater impact in C++ where they can be used with const variables and enforcement of template parameter constraints at instantiation time.

5 comments:

  1. I think this is what you had in mind:
    -# _Static_assert(1, "Not a standard compliant compiler");
    + _Static_assert(0, "Not a standard compliant compiler");

    ReplyDelete
  2. Thanks Petr for pointing this out, I have made the correction.

    ReplyDelete
  3. in C++11, static_assert cannot be used with const variables, only constexpr statements.

    ReplyDelete
  4. Rubenvb, I think you are getting confused with C++11, as there is no constexpr defined in C11.

    From an applied standpoint, I can only speak for using gcc. What gcc considers a constant expression is varied and depends on many factors (including the compiler version) and doesn't even have to be defined as constant. For example, in gcc 4.7.3, the following:

    static char *mystr = "mystr";

    int checkit(void) {
    /* this is a gcc extension */
    return __builtin_constant_p(mystr[3]);
    }

    would *indeed* return one! I haven't played with C11 yet, but I presume that the expression:

    _Static_assert(mystr[3] == 't', "not equal 't'");

    would also not fail.

    ReplyDelete
    Replies
    1. Oops, I should add that this only remains true as long as nothing alters the "buffer" pointed to by the variable mystr, nor exposes the "pointer" to anything outside of the translation unit. The reason I quoted these words is that, until such a time when code is encountered that potentially alters the buffer or pointer, or exposes the buffer (or address of the pointer) outside of the translation unit, these concepts are purely theoretical and the compiler may (as gcc does) completely omit them from the generated object file. Of course, a modern system wouldn't allow you to alter the string anyway, since it would put it in read-only memory, but the same holds true were it declared as

      static char mystr[6] = {'m', 'y', 's', 't', 'r', 0};

      Delete