Affermazione statica in C


Risposte:


91

Lo standard C11 aggiunge la _Static_assertparola chiave.

Questo è implementato da gcc-4.6 :

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.


4
[... sembra essere implementato da gcc, da clang ...] Puoi essere più assertivo che questo ;-) _Static_assertfa parte dello standard C11 e qualsiasi compilatore che supporti C11 lo avrà.
PP

1
Può essere utilizzato nell'ambito del file (al di fuori di qualsiasi funzione)? Perché ottengo 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)
user10607

@ user10607 Sono sorpreso che non funzioni .. Aspetta, ti manca una citazione alla fine della stringa di errore. Mettilo dentro e torna indietro. Questo funziona per me su gcc-4.9: _Static_assert( sizeof(int) == sizeof(long int), "Error!");sul mio computer ottengo l'errore.
emsr

Ho gcc 4.8.2 su Ubuntu. La citazione mancante era un errore di battitura nel commento (l'avevo in codice). Questa è la prima riga di un file dopo l'inclusione di un paio di intestazioni. Il compilatore mi dà due identici errori: 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))
user10607

2
@ user10607 Ho anche dovuto specificare -std = gnu11 sulla riga di comando. Sono davvero sorpreso che ci sia una differenza tra 4.8 e 4.8. Ho una fonte con una sola riga. Ho anche usato lo standard C _Static_assertnon quello C ++ static_assert. È necessario `#include <assert.h> per ottenere la macro static_assert.
emsr

93

Funziona in ambito funzione e non funzione (ma non all'interno di struct, unioni).

  1. 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

  2. La macro potrebbe o dovrebbe essere modificata per generare un nome univoco per il typedef (cioè concatenare __LINE__alla fine del static_assert_...nome)

  3. 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__

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:

Ed è così che sembra:


1
In Visual Studio dice solo "pedice negativo", senza menzionare il nome della variabile ...
szx

Nordic Mainframe - l'opzione 3 nella tua risposta non funziona su clang.
Elazar

1
Per quanto riguarda l'ultima soluzione (specifica per GCC 4.3 +): questa è molto potente, poiché può controllare tutto ciò che l'ottimizzatore può capire, ma fallisce se l'ottimizzazione non è abilitata. Il livello minimo di ottimizzazione ( -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.
Søren Løvborg

Nello snippet di codice con la versione LINE (AGGIORNAMENTO: Per completezza, ecco la versione con `LINE), durante la compilazione, si verifica un errore alla riga (STATIC_ASSERT (X, static_assertion_at_line _ ## L)), che può essere corretto aggiungendone un altro livello come di seguito: #define COMPILE_TIME_ASSERT4 (X, L) static_assert (X, # L); #define COMPILE_TIME_ASSERT3 (X, L) COMPILE_TIME_ASSERT3 (X, "" Asserzione a: ## L "");
domenica

Uso qualcosa di simile alla __LINE__versione in gcc 4.1.1 ... con occasionale fastidio quando due intestazioni diverse ne hanno una sulla stessa riga numerata!
MM

10

cl

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).

Adesso

sotto cldà:

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"


4

Da Wikipedia :


15
Sarebbe meglio se ti collegassi alla vera fonte: jaggersoft.com/pubs/CVu11_3.html
Matt Joiner

Non funziona in gcc 4.6 - dice "l'etichetta del case non si riduce a una costante intera". Ha un punto.
Liosan

entrambi probabilmente vi siete spostati mooolto avanti ormai, ma ho finito per scrivere il mio (vedi la mia risposta ). Ho usato il tuo link @MattJoiner per aiutarmi
Hashbrown

E se puoi essere disturbato, fammi sapere se funziona per te, @Liosan. Ho appena iniziato ad approfondire il C ++, quindi sono arrivato tardi alla festa
Hashbrown

Per quanto riguarda Visual C ++, ha static_assert integrato dalla versione 2010 e funziona in entrambe le modalità c ++ e c. Tuttavia, non dispone di c99 _Static_assert integrato.
ddbug

3

Vorrei non consiglia di utilizzare la soluzione con un typedef:

typedefNON è 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:

Lo consiglierei invece (su C99):

A causa della staticparola chiave, l'array verrà definito in fase di compilazione. Si noti che questa affermazione funzionerà solo con i CONDquali 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.


4
Anche se questo funzionerebbe, aumenterebbe anche i requisiti di memoria.
sherrellbc

1
errore: "static_assertion_INVALID_CHAR_SIZE" definito ma non utilizzato [-Werror = unused-variable]
Alex

2

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 :


1

Il modo classico consiste nell'usare un array:

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.


Racchiudere questo in una #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.
Gabriel Staples

Non sono d'accordo Il compilatore vede le macro pensate e fornisce un messaggio più confuso.
Paolo.Bolzoni

1

Da Perl, in particolare la perl.hlinea 3455 ( <assert.h>inclusa in anticipo):

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_ENDsono macro che si espandono rispettivamente in do/ while (0).


1

Perché:

  1. _Static_assert() è ora definito in gcc per tutte le versioni di C e
  2. static_assert() è definito in C ++ 11 e versioni successive

La seguente semplice macro per STATIC_ASSERT()quindi funziona in:

  1. C ++:
    1. C ++ 11 ( g++ -std=c++11) o versioni successive
  2. C:
    1. gcc -std=c90
    2. gcc -std=c99
    3. gcc -std=c11
    4. gcc (nessuno standard specificato)

Definisci STATIC_ASSERTcome segue:

Ora usalo:

Esempi:

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_assertfa 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);
^

Risultati completi dei test qui:

Relazionato:

  1. Utilizza static_assert per controllare i tipi passati alla macro [la mia risposta]
    1. https://en.cppreference.com/w/cpp/types/is_same
    2. https://en.cppreference.com/w/cpp/language/decltype
  2. Usa static_assert per controllare i tipi passati alla macro
  3. Come utilizzare l'asserzione statica in C per controllare i tipi di parametri passati a una macro

1
Perché così complicato, quando c'è una static_assertmacro in assert.h?
Arrivederci SE

@KamiKaze, sono sorpreso dalla tua domanda, perché sembra che tu non abbia effettivamente letto la mia risposta? La seconda riga della mia risposta dice tutto: "static_assert () è definito in C ++ 11 e versioni successive". Pertanto, static_assert()non è affatto disponibile in C. Vedi anche qui: en.cppreference.com/w/cpp/language/static_assert - mostra che static_assertesiste "(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 #definesecondi.
Gabriel Staples

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_assertnon è 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.
Arrivederci SE

> "_Static_assert non è disponibile in c99 e c90 in gcc (solo in gnu99 e gnu90)". Capisco quello che vuoi dire. È un'estensione gcc quindi hai ragione. > "Fondamentalmente fai molto lavoro extra". Non sono d'accordo; 2 definizione estremamente semplice non è affatto "un sacco" di lavoro extra. Detto questo, capisco cosa intendi ora. Continuo a pensare che quello che ho fatto sia utile e aggiunge valore al corpo di conoscenze e risposte presentate qui, quindi non penso che meriti il ​​voto negativo. Inoltre, il mio errore nel dire "C90 e versioni successive" invece di "gcc C90 e versioni successive", o "g90 e versioni successive", era solo nel mio commento sopra, non nella mia risposta.
Gabriel Staples

Poiché era effettivamente sbagliato, un voto negativo era giustificato. Se vuoi correggere le affermazioni sbagliate, controllerò di nuovo la risposta e potrei ritirare il mio voto negativo. Ancora aggiungere tale codice se non necessario (quindi se non si lavora con gnu90 e gnu99) non è vantaggioso per la chiarezza e aggiunge più confusione. Se hai il caso d'uso, potrebbe valerne la pena. Ma mi chiedo quale sia la rarità del caso d'uso in cui è richiesta la compatibilità con gnu99 / 90 e c ++ 11.
Arrivederci SE

0

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_ASSERTnormalmente (puoi scriverlo due volte nella stessa funzione se vuoi) e usa GLOBAL_STATIC_ASSERTal di fuori delle funzioni con una frase univoca come primo parametro.


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 predgelato 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 variableavvisi.
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. È uniquenecessario 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 ++


2
Non ci sono spazi dei nomi in C.
martinkunev

ah, whoops, ho letto male la domanda. Sembra che io sia venuto qui in cerca di una risposta a C ++ comunque (guardando l'ultima riga della mia risposta), quindi la lascio qui nel caso in cui altri facciano lo stesso
Hashbrown

0

Funziona, con l'opzione "Rimuovi inutilizzati" impostata. Posso usare una funzione globale per controllare i parametri globali.


1
Se funziona, lo farebbe solo nel sorgente di un eseguibile.
Coder

0

Questo ha funzionato per alcuni vecchi gcc. Mi dispiace di aver dimenticato quale versione era:

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.