Статические утверждают в C

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

Ответ 1

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

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

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

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

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

Ответ 2

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

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

    генерируется почти понятное сообщение,
  • Макрос может или должен быть изменен для создания уникального имени для typedef (т.е. concatenate __LINE__ в конце имени static_assert_...)

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

UPDATE: Для полноты, здесь версия с __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

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

#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

Ответ 3

сл

Я знаю, что вопрос явно упоминает 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

От Wikipedia:

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

COMPILE_TIME_ASSERT( BOOLEAN CONDITION );

Ответ 5

Я НЕ рекомендовал бы использовать решение с использованием 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 которые оцениваются во время компиляции. Он не будет работать (т.е. Произойдет сбой компиляции) с условиями, основанными на значениях в памяти, таких как значения, присвоенные переменным.

Ответ 6

Если вы используете макрос 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]

Ответ 7

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

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

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

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

Ответ 8

Так как:

  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 (не указан std)

Определите 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: примечание: в расширении макроса '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: примечание: в расширении макроса '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 [-W C++ 0x-compat]
STATIC_ASSERT (1> 2);
^
static_assert.c: в функции 'int main()
static_assert.c: 78: 99: ошибка: 'static_assert не был объявлен в этой области
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") fail")
^
static_assert.c: 88: 5: примечание: в расширении макроса '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. 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;
}

Ответ 9

Для тех из вас, кто хочет что-то действительно базовое и портативное, но не имеет доступа к функциям С++ 11, я написал только что.
Обычно используйте STATIC_ASSERT (вы можете записать его дважды в той же функции, если хотите) и использовать GLOBAL_STATIC_ASSERT вне функций с уникальной фразой в качестве первого параметра.

#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;
}

Объяснение:
<Суб > Сначала он проверяет, есть ли у вас реальное утверждение, которое вы определенно захотите использовать, если оно доступно.
Если вы этого не сделаете, получите свой pred текст и разделите его самостоятельно. Это делает две вещи.
Если он равен нулю, id est, утверждение не сработало, это приведет к делению на нулевую ошибку (арифметика вынуждена, потому что она пытается объявить массив).
Если он не равен нулю, он нормализует размер массива до 1. Поэтому, если утверждение прошло, вы все равно не захотите, чтобы его предикат был оценен как -1 (недействителен) или был 232442 (массивная трата пространства, IDK, если бы она была оптимизирована).
Для STATIC_ASSERT он завернут в фигурные скобки, что делает его блоком, который охватывает переменную assert, то есть вы можете писать ее много раз.
Он также отличает его от void, что является известным способом избавиться от предупреждений unused variable.
Для GLOBAL_STATIC_ASSERT вместо того, чтобы быть в блоке кода, он генерирует пространство имен. Пространства имен разрешены вне функций. A unique Идентификатор требуется, чтобы остановить любые конфликтующие определения, если вы используете этот один более одного раза. Суб >


Работал для меня на GCC и VS'12 С++

Ответ 10

Это работает с установкой "remove unused". Я могу использовать одну глобальную функцию для проверки глобальных параметров.

//
#ifndef __sassert_h__
#define __sassert_h__

#define _cat(x, y) x##y

#define _sassert(exp, ln) \
extern void _cat(ASSERT_WARNING_, ln)(void); \
if(!(exp)) \
{ \
    _cat(ASSERT_WARNING_, ln)(); \
}

#define sassert(exp) _sassert(exp, __LINE__)

#endif //__sassert_h__

//-----------------------------------------
static bool tab_req_set_relay(char *p_packet)
{
    sassert(TXB_TX_PKT_SIZE < 3000000);
    sassert(TXB_TX_PKT_SIZE >= 3000000);
    ...
}

//-----------------------------------------
Building target: ntank_app.elf
Invoking: Cross ARM C Linker
arm-none-eabi-gcc ...
../Sources/host_if/tab_if.c:637: undefined reference to `ASSERT_WARNING_637'
collect2: error: ld returned 1 exit status
make: *** [ntank_app.elf] Error 1
//

Ответ 11

Это сработало для некоторых старых 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

Ответ 12

От Perl, в частности, строка 3455 perl.h (<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) соответственно.