Qual é a melhor maneira de obter declarações estáticas de tempo de compilação em C (não C ++), com ênfase particular no GCC?
c
gcc
assert
compile-time
static-assert
Matt Joiner
fonte
fonte
_Static_assert
é parte do padrão C11 e qualquer compilador que suporte C11, terá.error: expected declaration specifiers or '...' before 'sizeof'
linhastatic_assert( sizeof(int) == sizeof(long int), "Error!);
(estou usando C, não C ++, a propósito)_Static_assert( sizeof(int) == sizeof(long int), "Error!");
No meu macine, recebo o erro.error: expected declaration specifiers or '...' before 'sizeof'
ANDerror: expected declaration specifiers or '...' before string constant
(ele está se referindo à"Error!"
string) (também: estou compilando com -std = c11. Ao colocar a declaração dentro de uma função, tudo funciona bem (falha e é bem-sucedido conforme o esperado))_Static_assert
não o C ++static_assert
. Você precisa `#include <assert.h> para obter a macro static_assert.Isso funciona no escopo funcional e não funcional (mas não dentro de structs, 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); }
Se a declaração de tempo de compilação não puder ser correspondida, uma mensagem quase inteligível é gerada pelo GCC
sas.c:4: error: size of array ‘static_assertion_this_should_be_true’ is negative
A macro pode ou deve ser alterada para gerar um nome exclusivo para o typedef (ou seja, concatenar
__LINE__
no final dostatic_assert_...
nome)Em vez de um ternário, isso poderia ser usado também, o
#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[2*(!!(COND))-1]
que funciona até mesmo no antigo compilador cc65 enferrujado (para a CPU 6502).ATUALIZAÇÃO: para completar, 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); }
ATUALIZAÇÃO2: código específico do GCC
O GCC 4.3 (eu acho) introduziu os atributos de função "erro" e "aviso". Se uma chamada para uma função com esse atributo não puder ser eliminada por meio da eliminação de código morto (ou outras medidas), um erro ou aviso será gerado. Isso pode ser usado para fazer declarações de tempo de compilação com descrições de falha definidas pelo usuário. Resta determinar como eles podem ser usados no escopo do namespace sem recorrer a uma função 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
fonte
-Og
) muitas vezes pode ser suficiente para que isso funcione, no entanto, e não deve interferir na depuração. Pode-se considerar fazer a declaração estática em um ambiente autônomo ou em tempo de execução se__OPTIMIZE__
(e__GNUC__
) não estiver definido.__LINE__
versão do gcc 4.1.1 ... com incômodo ocasional quando dois cabeçalhos diferentes têm um na mesma linha numerada!cl
Eu sei que a pergunta menciona explicitamente o gcc, mas apenas para completar, aqui está um ajuste para compiladores da Microsoft.
Usar o typedef de matriz de tamanho negativo não convence cl a 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 o typedeffing de uma estrutura, realmente precisamos usar nomes de tipo exclusivos.__LINE__
não corta a mostarda - é possível ter umCOMPILE_TIME_ASSERT()
na mesma linha em um cabeçalho e um arquivo de origem, e sua compilação será interrompida.__COUNTER__
vem em seu auxílio (e está 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á:Gcc também dá uma mensagem inteligível:
fonte
Da Wikipedia :
#define COMPILE_TIME_ASSERT(pred) switch(0){case 0:case pred:;} COMPILE_TIME_ASSERT( BOOLEAN CONDITION );
fonte
Eu NÃO recomendaria usar a solução com
typedef
:#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1]
A declaração de array com
typedef
palavra-chave NÃO tem garantia de ser avaliada em tempo de compilação. Por exemplo, o seguinte código no escopo do bloco será compilado:int invalid_value = 0; STATIC_ASSERT(invalid_value, this_should_fail_at_compile_time_but_will_not);
Eu recomendaria isso (em C99):
#define STATIC_ASSERT(COND,MSG) static int static_assertion_##MSG[(COND)?1:-1]
Por causa da
static
palavra - chave, o array será definido em tempo de compilação. Observe que esta declaração só funcionará com osCOND
que são avaliados em tempo de compilação. Ele não funcionará (ou seja, a compilação falhará) com condições baseadas em valores na memória, como valores atribuídos a variáveis.fonte
Se estiver usando a macro STATIC_ASSERT () com
__LINE__
, é possível evitar conflitos de número de linha entre uma entrada em um arquivo .c e uma entrada diferente em um arquivo de cabeçalho 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]
fonte
A maneira clássica é usar uma matriz:
char int_is_4_bytes_assertion[sizeof(int) == 4 ? 1 : -1];
Funciona porque se a asserção for verdadeira, o array tem tamanho 1 e é válido, mas se for falso, o tamanho -1 dá um erro de compilação.
A maioria dos compiladores mostra o nome da variável e aponta para a parte certa do código onde você pode deixar comentários eventuais sobre a asserção.
fonte
#define STATIC_ASSERT()
macro de tipo genérico e fornecendo mais exemplos genéricos e saída de amostra do compilador de seus exemplos genéricos usandoSTATIC_ASSERT()
daria a você muito mais votos positivos e faria essa técnica fazer mais sentido, eu acho.De Perl, especificamente a
perl.h
linha 3455 (<assert.h>
incluída de antemão):/* 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
estiver disponível (em<assert.h>
), ele será usado. Caso contrário, se a condição for falsa, um campo de bits com tamanho negativo é declarado, o que faz com que a compilação falhe.STMT_START
/STMT_END
são macros que se expandem parado
/while (0)
, respectivamente.fonte
Porque:
_Static_assert()
agora está definido no gcc para todas as versões de C, estatic_assert()
é definido em C ++ 11 e posteriorA seguinte macro simples para,
STATIC_ASSERT()
portanto, funciona em:g++ -std=c++11
) ou posteriorgcc -std=c90
gcc -std=c99
gcc -std=c11
gcc
(sem padrão especificado)Defina
STATIC_ASSERT
o seguinte:/* 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")
Agora use:
STATIC_ASSERT(1 > 2); // Output will look like: error: static assertion failed: "(1 > 2) failed"
Exemplos:
Testado no Ubuntu usando gcc 4.8.4:
Exemplo 1: boa
gcc
saída (ou seja: osSTATIC_ASSERT()
códigos funcionam, mas a condição era falsa, causando uma declaração em tempo de compilação):Exemplo 2: boa
g++ -std=c++11
saída (ou seja: osSTATIC_ASSERT()
códigos funcionam, mas a condição era falsa, causando uma declaração em tempo de compilação):Exemplo 3: saída em C ++ com falha (ou seja: o código de declaração não funciona corretamente, pois está usando uma versão de C ++ anterior a C ++ 11):
Resultados completos do teste aqui:
/* 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; }
Relacionado:
fonte
static_assert
macroassert.h
?static_assert()
não está disponível em C. Veja aqui também: en.cppreference.com/w/cpp/language/static_assert - mostra questatic_assert
existe "(desde C ++ 11)". A beleza da minha resposta é que funciona no C90 do gcc e posterior, bem como em qualquer C ++ 11 e posterior, em vez de apenas no C ++ 11 e posterior, comostatic_assert()
. Além disso, o que há de complicado em minha resposta? São apenas alguns#define
segundos.static_assert
é definido em C desde C11. É uma macro que se expande para_Static_assert
. en.cppreference.com/w/c/error/static_assert . Além disso, o contraste com a sua resposta_Static_assert
não está disponível em c99 e c90 no gcc (apenas no gnu99 e gnu90). Isso é compatível com o padrão. Basicamente, você faz muito trabalho extra, que só traz benefícios se compilado com gnu90 e gnu99 e que torna o caso de uso real insignificantemente pequeno.Para aqueles que desejam algo realmente básico e portátil, mas não têm acesso aos recursos do C ++ 11, escrevi exatamente a coisa certa.
Use
STATIC_ASSERT
normalmente (você pode escrever duas vezes na mesma função se quiser) e useGLOBAL_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, ele verifica se você tem a declaração real, que você definitivamente gostaria de usar se estiver disponível.
Se você não fizer isso, ele será
pred
declarado pegando seu icate e dividindo-o sozinho. Isso faz duas coisas.Se for zero, id est, a afirmação falhou, causará um erro de divisão por zero (a aritmética é forçada porque está tentando declarar uma matriz).
Se não for zero, normaliza o tamanho do array para
1
. Portanto, se a asserção for aprovada, você não gostaria que ela falhasse de qualquer maneira porque seu predicado foi avaliado como-1
(inválido) ou ser232442
(grande perda de espaço, IDK se fosse otimizado).Por
STATIC_ASSERT
estar entre colchetes, isso o torna um bloco, que define o escopo da variávelassert
, o que significa que você pode escrevê-lo muitas vezes.Ele também o converte em
void
, que é uma forma conhecida de se livrar deunused variable
avisos.Pois
GLOBAL_STATIC_ASSERT
, em vez de estar em um bloco de código, ele gera um namespace. Os namespaces são permitidos fora das funções. Umunique
identificador é necessário para interromper quaisquer definições conflitantes se você usar este mais de uma vez.Trabalhou para mim no GCC e VS'12 C ++
fonte
Isso funciona, com o conjunto de opções "remover não utilizado". Posso usar uma função 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 //
fonte
Isso funcionou para alguns gcc antigos. Desculpe ter esquecido qual era a versão:
#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
fonte