Який найкращий спосіб досягти статичних тверджень часу компіляції на C (а не на C ++), з особливим акцентом на GCC?
Який найкращий спосіб досягти статичних тверджень часу компіляції на C (а не на C ++), з особливим акцентом на GCC?
Відповіді:
Стандарт C11 додає _Static_assert
ключове слово.
_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!");
На моєму комп'ютері я отримую помилку.
error: expected declaration specifiers or '...' before 'sizeof'
AND 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]
що, здається, працює навіть на іржавому olde cc65 (для 6502 cpu) компілятора.
ОНОВЛЕННЯ:
Для повноти, ось версія з__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
-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 -Стіна -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 --це показує, що static_assert
існує "(починаючи з C ++ 11)". Краса моєї відповіді полягає в тому, що він працює у Ccc від gcc та пізніших версій, а також будь-який 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, матиме його.