Каков наилучший способ достижения статических утверждений времени компиляции на C (не C ++) с особым упором на GCC?
Каков наилучший способ достижения статических утверждений времени компиляции на C (не C ++) с особым упором на GCC?
Ответы:
Стандарт C11 добавляет _Static_assert
ключевое слово.
Это реализовано начиная с gcc-4.6 :
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
Первый слот должен быть интегральным постоянным выражением. Второй слот - это постоянный строковый литерал, который может иметь значение long ( _Static_assert(0, L"assertion of doom!")
).
Следует отметить, что это также реализовано в последних версиях clang.
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 ... время от времени раздражаю, когда два разных заголовка имеют один в одной пронумерованной строке!
Я знаю, что в вопросе явно упоминается 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
Из Википедии :
#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 и новее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()
коды работают, но условие было ложным, что привело к утверждению во время компиляции):
$ 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 ") не удалось")
^
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 ") не удалось")
^
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 [-Wc ++ 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 " ) не удалось ")
^
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. /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
_Static_assert
является частью стандарта C11, и любой компилятор, поддерживающий C11, получит его.