Каков наилучший способ достижения статических утверждений времени компиляции на C (не C ++) с особым упором на GCC?
c
gcc
assert
compile-time
static-assert
Мэтт Джойнер
источник
источник
_Static_assert
является частью стандарта C11, и любой компилятор, поддерживающий C11, получит его.error: expected declaration specifiers or '...' before 'sizeof'
строкуstatic_assert( sizeof(int) == sizeof(long int), "Error!);
(кстати, я использую C, а не C ++)_Static_assert( sizeof(int) == sizeof(long int), "Error!");
на моем macine я получаю сообщение об ошибке.error: expected declaration specifiers or '...' before 'sizeof'
Иerror: expected declaration specifiers or '...' before string constant
(он имеет в виду"Error!"
строку) (также: я компилирую с -std = c11. При помещении объявления внутри функции все работает хорошо (терпит неудачу и завершается успешно, как ожидалось))_Static_assert
не C ++static_assert
. Вам нужно `#include <assert.h> получить макрос static_assert.Это работает в функциональной и нефункциональной области (но не внутри структур и объединений).
#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); }
Если утверждение времени компиляции не может быть сопоставлено, то GCC генерирует почти понятное сообщение.
sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
Макрос можно или нужно изменить, чтобы сгенерировать уникальное имя для typedef (т. Е. Объединить
__LINE__
в концеstatic_assert_...
имени)Вместо троичного, это тоже может быть использовано,
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
что работает даже на ржавом компиляторе old 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 (я полагаю) представил атрибуты функции «ошибка» и «предупреждение». Если вызов функции с этим атрибутом не может быть устранен с помощью удаления мертвого кода (или других мер), генерируется ошибка или предупреждение. Это можно использовать для создания утверждений времени компиляции с определенными пользователем описаниями ошибок. Осталось определить, как их можно использовать в области пространства имен, не прибегая к фиктивной функции:
#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
источник
-Og
) часто бывает достаточно для того, чтобы это работало, и он не должен мешать отладке. Можно подумать о том, чтобы статическое утверждение было бездействующим или утверждением времени выполнения, если__OPTIMIZE__
(и__GNUC__
) не определено.__LINE__
версию в gcc 4.1.1 ... время от времени раздражаю, когда два разных заголовка имеют один в одной пронумерованной строке!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
дает:Gcc также дает внятное сообщение:
источник
Из Википедии :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
источник
Я бы НЕ рекомендовал использовать решение, используя
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
теми, которые оцениваются во время компиляции. Он не будет работать (т.е. компиляция завершится с ошибкой) с условиями, которые основаны на значениях в памяти, таких как значения, присвоенные переменным.источник
При использовании макроса 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]
источник
Классический способ - использовать массив:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
Это работает, потому что если утверждение истинно, массив имеет размер 1 и он действителен, но если он ложен, размер -1 дает ошибку компиляции.
Большинство компиляторов будут отображать имя переменной и указывать на правую часть кода, где вы можете оставлять возможные комментарии к утверждению.
источник
#define STATIC_ASSERT()
макроса универсального типа и предоставление более общих примеров и примеров вывода компилятора из ваших общих примеровSTATIC_ASSERT()
даст вам гораздо больше голосов и, я думаю, сделает этот метод более понятным.Из 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)
соответственно.источник
Так как:
_Static_assert()
теперь определен в gcc для всех версий C, иstatic_assert()
определен в C ++ 11 и новееПоэтому следующий простой макрос for
STATIC_ASSERT()
работает в:g++ -std=c++11
) или новееgcc -std=c90
gcc -std=c99
gcc -std=c11
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()
коды работают, но условие было ложным, что привело к утверждению во время компиляции):Пример 2: хороший
g++ -std=c++11
результат (то есть:STATIC_ASSERT()
коды работают, но условие было ложным, что привело к утверждению во время компиляции):Пример 3: сбой вывода C ++ (то есть: код утверждения не работает должным образом, поскольку он использует версию C ++ до C ++ 11):
Полные результаты тестирования здесь:
/* static_assert.c - test static asserts in C and C++ using gcc compiler Gabriel Staples 4 Mar. 2019 To be posted in: 1. /programming/987684/does-gcc-have-a-built-in-compile-time-assert/987756#987756 2. /programming/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; }
Связанный:
источник
static_assert
макросassert.h
?static_assert()
он вообще недоступен в C. См. Также здесь: en.cppreference.com/w/cpp/language/static_assert --it показывает, чтоstatic_assert
существует «(начиная с C ++ 11)». Прелесть моего ответа в том, что он работает в gcc C90 и новее, а также в любом C ++ 11 и новее, а не только в C ++ 11 и новее, напримерstatic_assert()
. Кроме того, что сложного в моем ответе? Это всего пара#define
с.static_assert
определено в C, поскольку C11. Это макрос, который расширяется до_Static_assert
. en.cppreference.com/w/c/error/static_assert . Кроме того, в отличие от вашего ответа,_Static_assert
он недоступен в c99 и c90 в gcc (только в gnu99 и gnu90). Это соответствует стандарту. В основном вы делаете много дополнительной работы, которая приносит пользу только в том случае, если она скомпилирована с помощью gnu90 и gnu99, и что делает фактический вариант использования незначительно маленьким.Для тех из вас, кто хочет что-то действительно простое и портативное, но не имеет доступа к функциям C ++ 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
леденец и разделяя его на себя. Это делает две вещи.Если он равен нулю, т. Е. Утверждение не удалось, это вызовет ошибку деления на ноль (арифметика выполняется принудительно, потому что она пытается объявить массив).
Если он не равен нулю, он нормализует размер массива до
1
. Таким образом, если утверждение прошло успешно, вы не захотите, чтобы оно в любом случае завершилось ошибкой, потому что ваш предикат оценивается как-1
(недействительный) или будет232442
(огромная трата места, IDK, если он будет оптимизирован).Поскольку
STATIC_ASSERT
он заключен в фигурные скобки, это делает его блоком, охватывающим переменнуюassert
, то есть вы можете писать его много раз.Он также приводит его
void
, что является известным способом избавиться отunused variable
предупреждений.Ведь
GLOBAL_STATIC_ASSERT
вместо того, чтобы находиться в блоке кода, он генерирует пространство имен. Пространства имен разрешены вне функций.unique
Идентификатор требуется , чтобы остановить любые противоречащие друг другу определения , если вы используете более чем один раз этот.У меня работал над GCC и VS'12 C ++
источник
Это работает с установленной опцией «удалить неиспользуемые». Я могу использовать одну глобальную функцию для проверки глобальных параметров.
// #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 //
источник
Это сработало для некоторых старых 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
источник