Static assert em C

Qual é a melhor maneira de obter afirmações estáticas de tempo de compilation em C (não em C ++), com ênfase particular no GCC?

O padrão C11 adiciona a palavra-chave _Static_assert .

Isso é implementado desde o gcc-4.6 :

 _Static_assert (0, "assert1"); /* { dg-error "static assertion failed: \"assert1\"" } */ 

O primeiro slot precisa ser uma expressão constante integral. O segundo slot é um literal de string constante que pode ser longo ( _Static_assert(0, L"assertion of doom!") ).

Devo observar que isso também é implementado em versões recentes do clang.

Isso funciona no escopo de function e não function (mas não dentro de estruturas, uniões).

 #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); } 
  1. Se a asserção de tempo de compilation não puder ser correspondida, então uma mensagem quase inteligível é gerada pelo GCC sas.c:4: error: size of array 'static_assertion_this_should_be_true' is negative

  2. A macro pode ou deve ser alterada para gerar um nome exclusivo para o typedef (ou seja, concatenar __LINE__ no final do static_assert_... name)

  3. Em vez de um ternário, isso também poderia ser usado #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1] que funciona mesmo no oxidado olde cc65 (para o compilador de 6502 cpu).

ATUALIZAÇÃO: Por questões de integridade, aqui está a versão com __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: código específico do GCC

O GCC 4.3 (eu acho) introduziu os atributos de function “error” e “warning”. Se uma chamada para uma function com esse atributo não puder ser eliminada por meio da eliminação de código inativo (ou outras medidas), um erro ou aviso será gerado. Isso pode ser usado para fazer afirmações de tempo de compilation com descrições de falhas definidas pelo usuário. Resta determinar como eles podem ser usados ​​no escopo do namespace sem recorrer a uma function fictícia:

 #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() { } 

E é assim que parece:

 $ 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 

cl

Eu sei que a questão menciona explicitamente o gcc, mas apenas por completude aqui é um ajuste para os compiladores da Microsoft.

Usar o typedef de matriz de tamanho negativo não persuade cl para cuspir um erro decente. Apenas diz error C2118: negative subscript . Um campo de bits de largura zero se sai melhor nesse aspecto. Como isso envolve digitar uma estrutura, realmente precisamos usar nomes de tipo exclusivos. __LINE__ não corta a mostarda – é possível ter um COMPILE_TIME_ASSERT() na mesma linha em um header e um arquivo de origem, e sua compilation será quebrada. __COUNTER__ vem para o resgate (e tem sido no gcc desde 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__) 

Agora

 STATIC_ASSERT(sizeof(long)==7, use_another_compiler_luke) 

sob cl dá:

erro C2149: ‘static_assertion_failed_use_another_compiler_luke’: o campo de bit nomeado não pode ter largura zero

O Gcc também fornece uma mensagem inteligível:

erro: largura zero para o campo de bits ‘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 ); 

Se estiver usando a macro STATIC_ASSERT () com __LINE__ , é possível evitar conflitos entre números de linha entre uma input em um arquivo .c e uma input diferente em um arquivo de header, incluindo __INCLUDE_LEVEL__ .

Por exemplo :

 /* 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] 

A maneira clássica é usar uma matriz:

 char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1]; 

Isso funciona porque, se a afirmação for verdadeira, o array tem tamanho 1 e é válido, mas se for falso, o tamanho de -1 gera um erro de compilation.

A maioria dos compiladores mostrará o nome da variável e apontará para a parte direita do código, onde você poderá deixar comentários eventuais sobre a afirmação.

Para aqueles de vocês que querem algo realmente básico e portátil, mas não têm access aos resources do C ++ 11, escrevi apenas a coisa.
Use STATIC_ASSERT normalmente (você pode escrevê-lo duas vezes na mesma function, se quiser) e use GLOBAL_STATIC_ASSERT fora das funções com uma frase única como o primeiro parâmetro.

 #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; } 

Explicação:
Primeiro, verifica se você tem a afirmação real, o que você definitivamente desejaria usar se estivesse disponível.
Se você não o fizer, obtenha o seu predicado e divida-o sozinho. Isso faz duas coisas.
Se for zero, id est, a asserção falhou, causará um erro de divisão por zero (a aritmética é forçada porque está tentando declarar um array).
Se não for zero, normaliza o tamanho do array para 1 . Portanto, se a afirmação for aprovada, você não desejará que ela falhe de qualquer maneira, porque seu predicado foi avaliado como -1 (inválido) ou seja 232442 (perda massiva de espaço, IDK se for otimizado).
Para STATIC_ASSERT é empacotado em chaves, isso faz com que seja um bloco, que escopara a variável assert , o que significa que você pode escrevê-lo várias vezes.
Ele também o void , que é uma maneira conhecida de se livrar de avisos de unused variable .
Para GLOBAL_STATIC_ASSERT , em vez de estar em um bloco de códigos, ele gera um namespace. Namespaces são permitidos fora das funções. Um identificador unique é necessário para interromper quaisquer definições conflitantes se você usar essa uma mais de uma vez.


Trabalhei para mim no GCC e no VS’12 C ++

Isso funciona, com o conjunto de opções “remover não usado”. Eu posso usar uma function global para verificar os parâmetros globais.

 // #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 // 

Isso funcionou para algum antigo gcc. Desculpe que eu esqueci qual versão 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 

Eu não recomendaria usar a solução usando um typedef :

 #define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1] 

A declaração de matriz com a palavra-chave typedef NÃO é garantida para ser avaliada em tempo de compilation. Por exemplo, o código a seguir no escopo de bloco compilará:

 int invalid_value = 0; STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not); 

Eu recomendaria isso em vez disso (em C99):

 #define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1] 

Por causa da palavra-chave static , a matriz será definida em tempo de compilation. Observe que essa declaração só funcionará com o COND que é avaliado em tempo de compilation. Ele não funcionará com (ou seja, a compilation falhará) com condições baseadas em valores na memory, como valores atribuídos a variables.

Intereting Posts