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?
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?
Respostas:
O padrão C11 adiciona a _Static_assert
palavra - chave.
Isso é implementado desde 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 long ( _Static_assert(0, L"assertion of doom!")
).
Devo observar que isso também é implementado em versões recentes do clang.
error: expected declaration specifiers or '...' before 'sizeof'
linha static_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'
AND error: 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 do static_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
-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!
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 um COMPILE_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á:
erro C2149: 'static_assertion_failed_use_another_compiler_luke': campo de bit nomeado não pode ter largura zero
Gcc também dá uma mensagem inteligível:
erro: largura zero para 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 );
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 os COND
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.
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]
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.
#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 usando STATIC_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 para do
/ while (0)
, respectivamente.
_Static_assert()
agora está definido no gcc para todas as versões de C, e static_assert()
é definido em C ++ 11 e posteriorSTATIC_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"
Testado no Ubuntu usando gcc 4.8.4:
Exemplo 1: boa gcc
saída (ou seja: os STATIC_ASSERT()
códigos funcionam, mas a condição era falsa, causando uma declaração em tempo de compilação):
$ gcc -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: Na função 'main'
static_assert.c: 78: 38: erro: declaração estática falhou: "(1> 2) falhou"
#define STATIC_ASSERT (test_for_true ) _Static_assert ((test_for_true), "(" #test_for_true ") falhou")
^
static_assert.c: 88: 5: nota: na expansão da macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
Exemplo 2: boa g++ -std=c++11
saída (ou seja: os STATIC_ASSERT()
códigos funcionam, mas a condição era falsa, causando uma declaração em tempo de compilação):
$ g ++ -Wall -std = c ++ 11 -o static_assert static_assert.c && ./static_assert
static_assert.c: Na função 'int main ()'
static_assert.c: 74: 32: erro: falha de declaração estática: (1> 2)
#define _Static_assert static_assert / *static_assert
é parte do C ++ 11 ou posterior * /
^
static_assert.c: 78: 38: observação: na expansão da macro '_Static_assert'
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true ") falhou")
^
static_assert.c: 88: 5: nota: na expansão da macro 'STATIC_ASSERT'
STATIC_ASSERT (1> 2);
^
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):
$ g ++ -Wall -o static_assert static_assert.c && ./static_assert
static_assert.c: 88: 5: warning: identifier 'static_assert' é uma palavra-chave em C ++ 11 [-Wc ++ 0x-compat]
STATIC_ASSERT (1> 2 );
^
static_assert.c: Na função 'int main ()'
static_assert.c: 78: 99: erro: 'static_assert' não foi declarado neste escopo
#define STATIC_ASSERT (test_for_true) _Static_assert ((test_for_true), "(" #test_for_true " ) falhou ")
^
static_assert.c: 88: 5: nota: na expansão da 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 assert.h
?
static_assert()
não está disponível em C. Veja aqui também: en.cppreference.com/w/cpp/language/static_assert - mostra que static_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, como static_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 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, 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 ser 232442
(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 de unused 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. Um unique
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 ++
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
//
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
_Static_assert
é parte do padrão C11 e qualquer compilador que suporte C11, terá.