Qual è il modo migliore per ottenere affermazioni statiche in fase di compilazione in C (non C ++), con particolare enfasi su GCC?
Qual è il modo migliore per ottenere affermazioni statiche in fase di compilazione in C (non C ++), con particolare enfasi su GCC?
Risposte:
Lo standard C11 aggiunge la _Static_assert
parola chiave.
Questo è implementato da gcc-4.6 :
_Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */
Il primo slot deve essere un'espressione costante integrale. Il secondo slot è una stringa letterale costante che può essere long ( _Static_assert(0, L"assertion of doom!")
).
Devo notare che questo è implementato anche nelle versioni recenti di clang.
error: expected declaration specifiers or '...' before 'sizeof'
per la linea static_assert( sizeof(int) == sizeof(long int), "Error!);
(sto usando C non C ++ tra l'altro)
_Static_assert( sizeof(int) == sizeof(long int), "Error!");
sul mio computer ottengo l'errore.
error: expected declaration specifiers or '...' before 'sizeof'
AND error: expected declaration specifiers or '...' before string constant
(si riferisce alla "Error!"
stringa) (anche: sto compilando con -std = c11. Quando si inserisce la dichiarazione all'interno di una funzione tutto funziona bene (fallisce e riesce come previsto))
_Static_assert
non quello C ++ static_assert
. È necessario `#include <assert.h> per ottenere la macro static_assert.
Funziona in ambito funzione e non funzione (ma non all'interno di struct, unioni).
#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);
}
Se non è stato possibile far corrispondere l'asserzione in fase di compilazione, GCC genera un messaggio quasi comprensibile sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
La macro potrebbe o dovrebbe essere modificata per generare un nome univoco per il typedef (cioè concatenare __LINE__
alla fine del static_assert_...
nome)
Invece di un ternario, potrebbe essere usato anche questo #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
che funziona anche sul vecchio compilatore cc65 (per la cpu 6502) arrugginito.
AGGIORNAMENTO:
Per completezza, ecco la versione con__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);
}
AGGIORNAMENTO2: codice specifico GCC
GCC 4.3 (immagino) ha introdotto gli attributi delle funzioni "errore" e "avviso". Se una chiamata a una funzione con quell'attributo non può essere eliminata tramite l'eliminazione del codice inattivo (o altre misure), viene generato un errore o un avviso. Questo può essere usato per fare affermazioni in fase di compilazione con descrizioni di errori definite dall'utente. Resta da determinare come possono essere utilizzati nell'ambito dello spazio dei nomi senza ricorrere a una funzione fittizia:
#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()
{
}
Ed è così che sembra:
$ 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
) può essere spesso sufficiente perché funzioni, tuttavia, e non dovrebbe interferire con il debug. Si può considerare di rendere l'asserzione statica un'asserzione no-op o runtime se __OPTIMIZE__
(e __GNUC__
) non è definito.
__LINE__
versione in gcc 4.1.1 ... con occasionale fastidio quando due intestazioni diverse ne hanno una sulla stessa riga numerata!
So che la domanda menziona esplicitamente gcc, ma solo per completezza ecco un tweak per i compilatori Microsoft.
L'uso del typedef dell'array di dimensioni negative non convince cl a sputare un errore decente. Dice solo error C2118: negative subscript
. Un bitfield a larghezza zero si comporta meglio sotto questo aspetto. Poiché questo implica la digitazione di una struttura, abbiamo davvero bisogno di usare nomi di tipo univoci. __LINE__
non taglia la senape: è possibile avere un COMPILE_TIME_ASSERT()
sulla stessa riga in un'intestazione e in un file sorgente e la compilazione si interromperà. __COUNTER__
viene in soccorso (ed è stato in gcc dalla 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__)
Adesso
STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke)
sotto cl
dà:
errore C2149: 'static_assertion_failed_use_another_compiler_luke': il campo bit denominato non può avere larghezza zero
Gcc dà anche un messaggio intelligibile:
errore: larghezza zero per il campo di bit "static_assertion_failed_use_another_compiler_luke"
Da Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;}
COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
Vorrei non consiglia di utilizzare la soluzione con un typedef
:
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
typedef
NON è garantito che la dichiarazione dell'array con la parola chiave venga valutata in fase di compilazione. Ad esempio, il codice seguente nell'ambito del blocco verrà compilato:
int invalid_value = 0;
STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);
Lo consiglierei invece (su C99):
#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]
A causa della static
parola chiave, l'array verrà definito in fase di compilazione. Si noti che questa affermazione funzionerà solo con i COND
quali vengono valutati in fase di compilazione. Non funzionerà con (cioè la compilazione fallirà) con condizioni basate sui valori in memoria, come i valori assegnati alle variabili.
Se si utilizza la macro STATIC_ASSERT () con __LINE__
, è possibile evitare conflitti di numeri di riga tra una voce in un file .c e una voce diversa in un file di intestazione includendo __INCLUDE_LEVEL__
.
Per esempio :
/* 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]
Il modo classico consiste nell'usare un array:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
Funziona perché se l'asserzione è vera l'array ha dimensione 1 ed è valido, ma se è falso la dimensione -1 dà un errore di compilazione.
La maggior parte dei compilatori mostra il nome della variabile e punta alla parte destra del codice dove è possibile lasciare eventuali commenti sull'asserzione.
#define STATIC_ASSERT()
macro di tipo generico e fornire esempi più generici e l'output del compilatore di esempio dai tuoi esempi generici usando STATIC_ASSERT()
ti darebbe molti più voti positivi e renderebbe questa tecnica più sensata, credo.
Da Perl, in particolare la perl.h
linea 3455 ( <assert.h>
inclusa in anticipo):
/* 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
Se static_assert
è disponibile (da <assert.h>
), viene utilizzato. Altrimenti, se la condizione è falsa, viene dichiarato un campo di bit con una dimensione negativa, che causa il fallimento della compilazione.
STMT_START
/ STMT_END
sono macro che si espandono rispettivamente in do
/ while (0)
.
_Static_assert()
è ora definito in gcc per tutte le versioni di C e static_assert()
è definito in C ++ 11 e versioni successiveSTATIC_ASSERT()
quindi funziona in:g++ -std=c++11
) o versioni successivegcc -std=c90
gcc -std=c99
gcc -std=c11
gcc
(nessuno standard specificato)Definisci STATIC_ASSERT
come segue:
/* 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")
Ora usalo:
STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed"
Testato in Ubuntu utilizzando gcc 4.8.4:
Esempio 1:gcc
output buono (ovvero: i STATIC_ASSERT()
codici funzionano, ma la condizione era falsa, causando un'asserzione in fase di compilazione):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: Nella funzione 'main'
static_assert.c: 78: 38: errore: asserzione statica non riuscita: "(1> 2) non riuscita"
#define STATIC_ASSERT (test_for_true ) _Static_assert ((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c: 88: 5: nota: in espansione della macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Esempio 2:g++ -std=c++11
output buono (ovvero: i STATIC_ASSERT()
codici funzionano, ma la condizione era falsa, causando un'asserzione in fase di compilazione):
$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: Nella funzione 'int main ()'
static_assert.c: 74: 32: errore: asserzione statica non riuscita: (1> 2) non riuscito
#define _Static_assert static_assert / *static_assert
fa parte di C ++ 11 o successivo * /
^
static_assert.c: 78: 38: nota: nell'espansione della macro '_Static_assert'
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") failed")
^
static_assert.c: 88: 5: nota: in espansione della macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Esempio 3: output C ++ non riuscito (ovvero: il codice di asserzione non funziona correttamente, poiché utilizza una versione di C ++ precedente a C ++ 11):
$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: warning: identifier 'static_assert' è una parola chiave in C ++ 11 [-Wc ++ 0x-compat]
STATIC_ASSERT (1> 2 );
^
static_assert.c: Nella funzione 'int main ()'
static_assert.c: 78: 99: errore: 'static_assert' non è stato dichiarato in questo ambito
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true " ) failed ")
^
static_assert.c: 88: 5: nota: in espansione della macro '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
macro in assert.h
?
static_assert()
non è affatto disponibile in C. Vedi anche qui: en.cppreference.com/w/cpp/language/static_assert - mostra che static_assert
esiste "(da C ++ 11)". La bellezza della mia risposta è che funziona in C90 di gcc e versioni successive, così come in qualsiasi C ++ 11 e versioni successive, invece che solo in C ++ 11 e versioni successive, come static_assert()
. Inoltre, cosa c'è di complicato nella mia risposta? Sono solo un paio di #define
secondi.
static_assert
è definito in C a partire da C11. È una macro che si espande _Static_assert
. en.cppreference.com/w/c/error/static_assert . Inoltre e in contrasto con la tua risposta _Static_assert
non è disponibile in c99 e c90 in gcc (solo in gnu99 e gnu90). Questo è conforme allo standard. Fondamentalmente fai un sacco di lavoro extra, che porta benefici solo se compilato con gnu90 e gnu99 e che rende il caso d'uso attuale insignificantemente piccolo.
Per quelli di voi che desiderano qualcosa di veramente semplice e portatile ma non hanno accesso alle funzionalità di C ++ 11, ho scritto proprio la cosa.
Usalo STATIC_ASSERT
normalmente (puoi scriverlo due volte nella stessa funzione se vuoi) e usa GLOBAL_STATIC_ASSERT
al di fuori delle funzioni con una frase univoca come primo parametro.
#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;
}
Spiegazione: per
prima cosa controlla se hai la vera affermazione, che vorresti sicuramente usare se fosse disponibile.
Se non lo fai si afferma prendendo il tuo pred
gelato e dividendolo da solo. Questo fa due cose.
Se è zero, id est, l'asserzione non è riuscita, causerà un errore di divisione per zero (l'aritmetica è forzata perché sta cercando di dichiarare un array).
Se non è zero, normalizza la dimensione dell'array a 1
. Quindi, se l'asserzione è passata, non vorresti che fallisse comunque perché il tuo predicato è stato valutato come -1
(non valido) o essere 232442
(enorme spreco di spazio, IDK se fosse ottimizzato).
Poiché STATIC_ASSERT
è racchiuso tra parentesi graffe, questo lo rende un blocco, che definisce l'ambito della variabileassert
, il che significa che puoi scriverlo molte volte.
Lo lancia anche su void
, che è un modo noto per eliminare gli unused variable
avvisi.
Perché GLOBAL_STATIC_ASSERT
, invece di essere in un blocco di codice, genera uno spazio dei nomi. Gli spazi dei nomi sono consentiti al di fuori delle funzioni. È unique
necessario un identificatore per interrompere qualsiasi definizione in conflitto se si utilizza più di una volta.
Ha funzionato per me su GCC e VS'12 C ++
Funziona, con l'opzione "Rimuovi inutilizzati" impostata. Posso usare una funzione globale per controllare i parametri globali.
//
#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
//
Questo ha funzionato per alcuni vecchi gcc. Mi dispiace di aver dimenticato quale versione era:
#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
fa parte dello standard C11 e qualsiasi compilatore che supporti C11 lo avrà.