Jaki jest najlepszy sposób na osiągnięcie statycznych potwierdzeń czasu kompilacji w C (nie C ++), ze szczególnym uwzględnieniem GCC?
c
gcc
assert
compile-time
static-assert
Matt Joiner
źródło
źródło
_Static_assert
jest częścią standardu C11 i każdy kompilator obsługujący C11 będzie go miał.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'
ANDerror: 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ńcustatic_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
źródło
-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!kl
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 umieszczenieCOMPILE_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:Gcc daje również zrozumiały komunikat:
źródło
Z Wikipedii :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
źródło
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.źródło
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]
źródło
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.
źródło
#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życiuSTATIC_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 dodo
/while (0)
.źródło
Dlatego:
_Static_assert()
jest teraz zdefiniowany w gcc dla wszystkich wersji C istatic_assert()
jest zdefiniowany w C ++ 11 i nowszych wersjachDlatego następujące proste makro for
STATIC_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"
Przykłady:
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):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):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):
Pełne wyniki testu tutaj:
/* 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; }
Związane z:
źródło
static_assert
makroassert.h
?static_assert()
w ogóle nie jest dostępny w C. Zobacz też: en.cppreference.com/w/cpp/language/static_assert --it shows therestatic_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, jakstatic_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żywajGLOBAL_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 ++
źródło
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 //
źródło
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
źródło