Jaki jest najlepszy sposób na osiągnięcie statycznych potwierdzeń czasu kompilacji w C (nie C ++), ze szczególnym uwzględnieniem GCC?
Jaki jest najlepszy sposób na osiągnięcie statycznych potwierdzeń czasu kompilacji w C (nie C ++), ze szczególnym uwzględnieniem GCC?
Odpowiedzi:
Standard C11 dodaje _Static_assert
słowo kluczowe.
Jest to zaimplementowane od gcc-4.6 :
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
Pierwsza szczelina musi być integralnym wyrażeniem stałym. Drugi slot to stały literał ciągu, którym może być long ( _Static_assert(0, L"assertion of doom!")
).
Powinienem zauważyć, że jest to również zaimplementowane w najnowszych wersjach clang.
error: expected declaration specifiers or '...' before 'sizeof'
linię static_assert( sizeof(int) == sizeof(long int), "Error!);
(przy okazji używam C, a nie C ++)
_Static_assert( sizeof(int) == sizeof(long int), "Error!");
Na moim komputerze pojawia się błąd.
error: expected declaration specifiers or '...' before 'sizeof'
AND error: expected declaration specifiers or '...' before string constant
(odwołuje się do ciągu "Error!"
znaków) (również: kompiluję z -std = c11. Podczas umieszczania deklaracji wewnątrz funkcji wszystko działa dobrze (kończy się niepowodzeniem i kończy się sukcesem zgodnie z oczekiwaniami))
_Static_assert
nie języka C ++ static_assert
. Musisz `#include <assert.h>, aby uzyskać makro static_assert.
Działa to w zakresie funkcyjnym i niefunkcyjnym (ale nie wewnątrz struktur, związków).
#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);
}
Jeśli asercji czasu kompilacji nie można dopasować, GCC generuje prawie zrozumiały komunikat sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
Makro można lub należy zmienić, aby wygenerować unikalną nazwę dla typu (tj. Konkatenację __LINE__
na końcu static_assert_...
nazwy)
Zamiast trójskładnika można go również użyć, #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
co zdarza się, że działa nawet na zardzewiałym, starym kompilatorze cc65 (dla procesora 6502).
AKTUALIZACJA:
Ze względu na kompletność, oto wersja z__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: kod specyficzny dla GCC
GCC 4.3 (chyba) wprowadziło atrybuty funkcji „error” i „warning”. Jeśli wywołanie funkcji z tym atrybutem nie mogło zostać wyeliminowane poprzez eliminację martwego kodu (lub inne środki), generowany jest błąd lub ostrzeżenie. Może to służyć do tworzenia potwierdzeń czasu kompilacji ze zdefiniowanymi przez użytkownika opisami niepowodzeń. Pozostaje ustalić, jak można ich używać w zakresie przestrzeni nazw bez uciekania się do funkcji fikcyjnej:
#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()
{
}
A tak to wygląda:
$ 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
) może jednak często wystarczać, aby to zadziałało i nie powinien kolidować z debugowaniem. Można rozważyć uczynienie ze statycznego potwierdzenia braku operacji lub działania wykonawczego, jeśli __OPTIMIZE__
(i __GNUC__
) nie jest zdefiniowane.
__LINE__
wersji w gcc 4.1.1 ... z czasami irytacją, gdy dwa różne nagłówki mają jeden w tej samej numerowanej linii!
Wiem, że pytanie wyraźnie wspomina o gcc, ale dla kompletności tutaj jest poprawka dla kompilatorów Microsoft.
Użycie ujemnego typu array typedef nie przekonuje cl do wyplucia przyzwoitego błędu. Po prostu mówi error C2118: negative subscript
. Pod tym względem pole bitowe o zerowej szerokości wypada lepiej. Ponieważ obejmuje to typedeffing struktury, naprawdę musimy używać unikalnych nazw typów. __LINE__
nie tnie musztardy - możliwe jest umieszczenie COMPILE_TIME_ASSERT()
w tej samej linii nagłówka i pliku źródłowego, a kompilacja się zepsuje. __COUNTER__
przychodzi na ratunek (i jest w gcc od 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__)
Teraz
STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
pod cl
daje:
błąd C2149: „static_assertion_failed_use_another_compiler_luke”: nazwane pole bitowe nie może mieć zerowej szerokości
Gcc daje również zrozumiały komunikat:
błąd: zerowa szerokość dla pola bitowego „static_assertion_failed_use_another_compiler_luke”
Z Wikipedii :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
Chciałbym NIE zalecamy użycie rozwiązanie wykorzystujące typedef
:
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
Deklaracja tablicy ze typedef
słowem kluczowym NIE gwarantuje, że zostanie oceniona w czasie kompilacji. Na przykład następujący kod w zakresie blokowym zostanie skompilowany:
int invalid_value = 0;
STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);
Poleciłbym to zamiast tego (na C99):
#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]
Ze względu na static
słowo kluczowe tablica zostanie zdefiniowana w czasie kompilacji. Zauważ, że to potwierdzenie będzie działać tylko z tymi, COND
które są oceniane w czasie kompilacji. Nie będzie działać (tj. Kompilacja się nie powiedzie) z warunkami opartymi na wartościach w pamięci, takich jak wartości przypisane do zmiennych.
Jeśli używasz makra STATIC_ASSERT () z __LINE__
, możliwe jest uniknięcie kolizji numerów linii między wpisem w pliku .c a innym wpisem w pliku nagłówkowym przez dołączenie __INCLUDE_LEVEL__
.
Na przykład :
/* 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]
Klasycznym sposobem jest użycie tablicy:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
Działa, ponieważ jeśli twierdzenie jest prawdziwe, tablica ma rozmiar 1 i jest poprawne, ale jeśli jest fałszywe, rozmiar -1 powoduje błąd kompilacji.
Większość kompilatorów pokaże nazwę zmiennej i wskaże prawą część kodu, w której można zostawić ewentualne komentarze dotyczące potwierdzenia.
#define STATIC_ASSERT()
makrze typu ogólnego i dostarczenie bardziej ogólnych przykładów i przykładowych danych wyjściowych kompilatora z przykładów ogólnych przy użyciu STATIC_ASSERT()
dałoby o wiele więcej pozytywnych głosów i sprawiłoby, że ta technika miałaby większy sens.
Z Perla, a konkretnie perl.h
linia 3455 ( <assert.h>
zawarta wcześniej):
/* 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
Jeśli static_assert
jest dostępny (od <assert.h>
), jest używany. W przeciwnym razie, jeśli warunek jest fałszywy, deklarowane jest pole bitowe o rozmiarze ujemnym, co powoduje niepowodzenie kompilacji.
STMT_START
/ STMT_END
to makra rozwijane odpowiednio do do
/ while (0)
.
_Static_assert()
jest teraz zdefiniowany w gcc dla wszystkich wersji C i static_assert()
jest zdefiniowany w C ++ 11 i nowszych wersjachSTATIC_ASSERT()
działa w:g++ -std=c++11
) lub nowszygcc -std=c90
gcc -std=c99
gcc -std=c11
gcc
(nie określono standardu)Zdefiniuj STATIC_ASSERT
w następujący sposób:
/* 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")
Teraz użyj:
STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed"
Testowane w Ubuntu przy użyciu gcc 4.8.4:
Przykład 1: dobry gcc
wynik (tj .: STATIC_ASSERT()
kody działają, ale warunek był fałszywy, powodując asert w czasie kompilacji):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: W funkcji „main”
static_assert.c: 78: 38: błąd: statyczne potwierdzenie nie powiodło się: „(1> 2) nie powiodło się”
#define STATIC_ASSERT (test_for_true ) _Static_assert ((test_for_true), "(" #test_for_true ") nie powiodło się")
^
static_assert.c: 88: 5: uwaga: w rozwinięciu makra 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Przykład 2: dobry g++ -std=c++11
wynik (tj .: STATIC_ASSERT()
kody działają, ale warunek był fałszywy, powodując asert w czasie kompilacji):
$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: In function 'int main ()'
static_assert.c: 74: 32: error: static assertion failed: (1> 2) nie powiodło się
#define _Static_assert static_assert / *static_assert
jest częścią C ++ 11 lub nowszego * /
^
static_assert.c: 78: 38: uwaga: w rozszerzeniu makra '_Static_assert'
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") nie powiodło się")
^
static_assert.c: 88: 5: uwaga: podczas rozwijania makra 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Przykład 3: nieudane wyjście C ++ (tj .: kod assert w ogóle nie działa poprawnie, ponieważ używa wersji C ++ przed C ++ 11):
$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: warning: identifier „static_assert” jest słowem kluczowym w C ++ 11 [-Wc ++ 0x-
Compatible ] STATIC_ASSERT (1> 2 );
^
static_assert.c: W funkcji 'int main ()'
static_assert.c: 78: 99: error: 'static_assert' nie zostało zadeklarowane w tym zakresie
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true " ) nie powiodło się ")
^
static_assert.c: 88: 5: uwaga: podczas rozwijania makra '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
makro assert.h
?
static_assert()
w ogóle nie jest dostępny w C. Zobacz też: en.cppreference.com/w/cpp/language/static_assert --it shows there static_assert
"(od C ++ 11)". Piękno mojej odpowiedzi polega na tym, że działa w C90 gcc i późniejszych, a także w każdym C ++ 11 i później, zamiast tylko w C ++ 11 i nowszych, jak static_assert()
. Co jest skomplikowanego w mojej odpowiedzi? To tylko kilka #define
sekund.
static_assert
jest zdefiniowany w C od C11. Jest to makro, które rozwija się do _Static_assert
. en.cppreference.com/w/c/error/static_assert . Dodatkowo, w przeciwieństwie do twojej odpowiedzi _Static_assert
nie jest dostępna w c99 i c90 w gcc (tylko w gnu99 i gnu90). Jest to zgodne z normą. Zasadniczo wykonujesz dużo dodatkowej pracy, która przyniesie korzyści tylko wtedy, gdy zostanie skompilowana z gnu90 i gnu99, a faktyczny przypadek użycia będzie nieznacznie mały.
Dla tych z Was, którzy chcą czegoś naprawdę prostego i przenośnego, ale nie mają dostępu do funkcji C ++ 11, napisałem właśnie to.
Używaj STATIC_ASSERT
normalnie (możesz napisać to dwukrotnie w tej samej funkcji, jeśli chcesz) i używaj GLOBAL_STATIC_ASSERT
poza funkcjami z unikalną frazą jako pierwszym parametrem.
#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;
}
Wyjaśnienie:
Najpierw sprawdza, czy masz prawdziwy assert, którego na pewno chciałbyś użyć, jeśli jest dostępny.
Jeśli tego nie zrobisz, to zapewnia, zdobywając swój pred
lód i dzieląc go samodzielnie. To robi dwie rzeczy.
Jeśli wynosi zero, id est, asercja się nie powiodła, spowoduje błąd dzielenia przez zero (arytmetyka jest wymuszona, ponieważ próbuje zadeklarować tablicę).
Jeśli nie jest zerem, normalizuje rozmiar tablicy do 1
. Więc jeśli asercja przeszła pomyślnie, i tak nie chciałbyś, aby zakończyło się niepowodzeniem, ponieważ predykat został oceniony jako -1
(nieprawidłowy) lub był 232442
(ogromne marnowanie miejsca, IDK, jeśli zostałby zoptymalizowany).
Ponieważ STATIC_ASSERT
jest zawinięty w nawiasy klamrowe, jest to blok, który określa zakres zmiennejassert
, co oznacza, że możesz to napisać wiele razy.
Rzuca go również na void
, co jest znanym sposobem na pozbycie się unused variable
ostrzeżeń.
Dla GLOBAL_STATIC_ASSERT
, zamiast w bloku kodu, generuje nazw. Przestrzenie nazw są dozwolone poza funkcjami. unique
Identyfikator wymagane jest, aby zatrzymać wszelkie sprzeczne definicje jeśli używasz ten jeden więcej niż raz.
Pracował dla mnie na GCC i VS'12 C ++
Działa to z ustawioną opcją „usuń nieużywane”. Mogę użyć jednej funkcji globalnej do sprawdzenia parametrów globalnych.
//
#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
//
To zadziałało dla niektórych starych gcc. Przepraszam, że zapomniałem jaka to była wersja:
#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
jest częścią standardu C11 i każdy kompilator obsługujący C11 będzie go miał.