Статическое утверждение в C - PullRequest
73 голосов
/ 02 августа 2010

Каков наилучший способ достижения статических утверждений времени компиляции в C (не в C ++), с особым акцентом на GCC?

Ответы [ 12 ]

79 голосов
/ 02 августа 2010

Это работает в функциональной и нефункциональной области (но не внутри структур, союзов).

#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. Если утверждение времени компиляции не может быть сопоставлено, то GCC sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative

  2. * 1010 генерируется почти понятное сообщениемакрос может или должен быть изменен для генерации уникального имени для typedef (то есть конкатенации __LINE__ в конце имени static_assert_...)
  3. Вместо троичного, это может бытьтакже используется #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1], который работает даже на ржавом компиляторе olde cc65 (для процессора 6502).

ОБНОВЛЕНИЕ: Для полноты, вотверсия с __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); 
}

ОБНОВЛЕНИЕ2: специальный код GCC

GCC 4.3 (я полагаю) ввел атрибуты функций «error» и «warning».Если вызов функции с этим атрибутом не может быть устранен посредством устранения мертвого кода (или других мер), то генерируется ошибка или предупреждение.Это может использоваться, чтобы сделать утверждения времени компиляции с определенными пользователем описаниями ошибок.Осталось определить, как их можно использовать в области имен, не прибегая к фиктивной функции:

#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()
{
}

И вот как это выглядит:

$ 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
73 голосов
/ 02 сентября 2011

C11 стандарт добавляет ключевое слово _Static_assert.

Это реализовано начиная с gcc-4.6 :

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

Первый слот должен быть целочисленным константным выражением. Второй слот является константным строковым литералом, который может быть длинным (_Static_assert(0, L"assertion of doom!")).

Я должен отметить, что это также реализовано в последних версиях clang.

11 голосов
/ 27 января 2011

cl

Я знаю, что этот вопрос явно упоминает gcc, но для полноты здесь приведена настройка для компиляторов Microsoft.

Использование массива отрицательного размера typedef не убеждает cl выплюнуть приличную ошибку.Это просто говорит error C2118: negative subscript.Битовое поле нулевой ширины лучше в этом отношении.Так как это включает определение типа структуры, нам действительно нужно использовать уникальные имена типов.__LINE__ не обрезает горчицу - возможно иметь COMPILE_TIME_ASSERT() в одной строке в заголовке и исходном файле, и ваша компиляция прервется.__COUNTER__ приходит на помощь (и он был в gcc с 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__)

Теперь

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

под cl дает:

ошибка C2149: «static_assertion_failed_use_another_compiler_luke»: именованное битовое поле не может иметь нулевую ширину

Gcc также дает понятное сообщение:

ошибка: нулевая ширина для битового поля 'static_assertion_failed_use_another_compiler_luke '

4 голосов
/ 02 августа 2010

Из Википедия :

#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
3 голосов
/ 03 августа 2018

Я бы НЕ рекомендовал бы использовать решение, используя typedef:

#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]

Объявление массива с ключевым словом typedef НЕ гарантированно оценивается во время компиляции. Например, будет скомпилирован следующий код в области блока:

int invalid_value = 0;
STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);

Я бы порекомендовал это вместо этого (на C99):

#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]

Из-за ключевого слова static массив будет определен во время компиляции. Обратите внимание, что это утверждение будет работать только с COND, которые оцениваются во время компиляции. Он не будет работать с (т. Е. Компиляция не удастся) с условиями, основанными на значениях в памяти, таких как значения, присвоенные переменным.

2 голосов
/ 16 мая 2017

При использовании макроса STATIC_ASSERT () с __LINE__ можно избежать столкновения номера строки между записью в файле .c и другой записью в файле заголовка, включив __INCLUDE_LEVEL__.

Например:

/* Trickery to create a unique variable name */
#define BOOST_JOIN( X, Y )      BOOST_DO_JOIN( X, Y )
#define BOOST_DO_JOIN( X, Y )   BOOST_DO_JOIN2( X, Y )
#define BOOST_DO_JOIN2( X, Y )  X##Y
#define STATIC_ASSERT(x)        typedef char \
        BOOST_JOIN( BOOST_JOIN(level_,__INCLUDE_LEVEL__), \
                    BOOST_JOIN(_assert_on_line_,__LINE__) ) [(x) ? 1 : -1]
1 голос
/ 05 марта 2019

Потому что:

  1. _Static_assert() теперь определено в gcc для всех версий C, а
  2. static_assert() определено в C ++ 11 и более поздних версиях

Следовательно, следующий простой макрос для STATIC_ASSERT() работает в:

  1. C ++:
    1. C ++ 11 (g++ -std=c++11) или новее
  2. C:
    1. gcc -std=c90
    2. gcc -std=c99
    3. gcc -std=c11
    4. gcc (стандарт не указан)

Определите STATIC_ASSERT следующим образом:

/* 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")

Теперь используйте его:

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

Примеры:

Протестировано в Ubuntu с использованием gcc 4.8.4:

Пример 1: хороший gcc вывод (то есть: коды STATIC_ASSERT() работают, но условие было ложным, вызывая утверждение во время компиляции):

$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: в функции «main»
static_assert.c: 78: 38: ошибка: статическое утверждение не удалось: «(1> 2) не удалось»
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") fail")
^
static_assert.c: 88: 5: note: в расширении макроса «STATIC_ASSERT»
STATIC_ASSERT (1> 2);
^

Пример 2: хороший g++ -std=c++11 вывод (то есть: коды STATIC_ASSERT() работают, но условие было ложным, вызывая утверждение во время компиляции):

$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: в функции int main ()
static_assert.c: 74: 32: ошибка: статическое утверждение не удалось: (1> 2) не удалось
#define _Static_assert static_assert / * static_assert является частью C ++ 11 или более поздней версии * /
^
static_assert.c: 78: 38: примечание: в расширении макроса _Static_assert
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") fail")
^
static_assert.c: 88: 5: note: в расширении макроса «STATIC_ASSERT»
STATIC_ASSERT (1> 2);
^

Пример 3: Сбой Вывод C ++ (т. Е. Код подтверждения вообще не работает должным образом, поскольку используется версия C ++ до C ++ 11):

$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: предупреждение: идентификатор «static_assert» является ключевым словом в C ++ 11 [-Wc ++ 0x-compat]
STATIC_ASSERT (1> 2);
^
static_assert.c: в функции int main () ’1105 * static_assert.c: 78: 99: ошибка: «static_assert» не был объявлен в этой области
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") не удалось")
^
static_assert.c: 88: 5: note: в расширении макроса STATIC_ASSERT
STATIC_ASSERT (1> 2);
^

Полные результаты теста здесь:

/*
static_assert.c
- test static asserts in C and C++ using gcc compiler

Gabriel Staples
4 Mar. 2019 

To be posted in:
1. /725445/est-li-v-gcc-vstroennoe-utverzhdenie-vremeni-kompilyatsii#725448
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;
}
1 голос
/ 12 марта 2015

Классическим способом является использование массива:

char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];

Это работает, потому что, если утверждение истинно, массив имеет размер 1 и он действителен, но если он ложен, то значение -1 дает ошибку компиляции.

Большинство компиляторов будут показывать имя переменной и указывать на правую часть кода, где вы можете оставлять возможные комментарии об утверждении.

0 голосов
/ 24 июня 2019

Из Perl, в частности perl.h строка 3455 (<assert.h> включено заранее):

/* STATIC_ASSERT_DECL/STATIC_ASSERT_STMT are like assert(), but for compile
   time invariants. That is, their argument must be a constant expression that
   can be verified by the compiler. This expression can contain anything that's
   known to the compiler, e.g. #define constants, enums, or sizeof (...). If
   the expression evaluates to 0, compilation fails.
   Because they generate no runtime code (i.e.  their use is "free"), they're
   always active, even under non-DEBUGGING builds.
   STATIC_ASSERT_DECL expands to a declaration and is suitable for use at
   file scope (outside of any function).
   STATIC_ASSERT_STMT expands to a statement and is suitable for use inside a
   function.
*/
#if (defined(static_assert) || (defined(__cplusplus) && __cplusplus >= 201103L)) && (!defined(__IBMC__) || __IBMC__ >= 1210)
/* static_assert is a macro defined in <assert.h> in C11 or a compiler
   builtin in C++11.  But IBM XL C V11 does not support _Static_assert, no
   matter what <assert.h> says.
*/
#  define STATIC_ASSERT_DECL(COND) static_assert(COND, #COND)
#else
/* We use a bit-field instead of an array because gcc accepts
   'typedef char x[n]' where n is not a compile-time constant.
   We want to enforce constantness.
*/
#  define STATIC_ASSERT_2(COND, SUFFIX) \
    typedef struct { \
        unsigned int _static_assertion_failed_##SUFFIX : (COND) ? 1 : -1; \
    } _static_assertion_failed_##SUFFIX PERL_UNUSED_DECL
#  define STATIC_ASSERT_1(COND, SUFFIX) STATIC_ASSERT_2(COND, SUFFIX)
#  define STATIC_ASSERT_DECL(COND)    STATIC_ASSERT_1(COND, __LINE__)
#endif
/* We need this wrapper even in C11 because 'case X: static_assert(...);' is an
   error (static_assert is a declaration, and only statements can have labels).
*/
#define STATIC_ASSERT_STMT(COND)      STMT_START { STATIC_ASSERT_DECL(COND); } STMT_END

Если доступно static_assert (от <assert.h>), оно используется. В противном случае, если условие ложно, объявляется битовое поле с отрицательным размером, что приводит к сбою компиляции.

STMT_START / STMT_END - макросы, расширяющиеся до do / while (0) соответственно.

0 голосов
/ 27 июля 2018

Это сработало для какого-то старого gcc.Извините, что я забыл, что это была за версия:

#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
...