Como posso usar “sizeof” em uma macro de pré-processador?

95

Existe alguma maneira de usar um sizeofem uma macro de pré-processador?

Por exemplo, houve uma tonelada de situações ao longo dos anos em que eu queria fazer algo como:

#if sizeof(someThing) != PAGE_SIZE
#error Data structure doesn't match page size
#endif

A coisa exata que estou verificando aqui está completamente inventada - o que quero dizer é que geralmente gosto de incluir esses tipos de (tamanho ou alinhamento) verificações de tempo de compilação para evitar que alguém modifique uma estrutura de dados que poderia desalinhar ou re- dimensionar coisas que os quebrariam.

Desnecessário dizer - não pareço ser capaz de usar um sizeofda maneira descrita acima.

Brad
fonte
Esta é a razão exata pela qual existem sistemas de construção.
Šimon Tóth
3
Esta é a razão exata pela qual as diretivas #error devem sempre estar entre aspas duplas (constante de caractere não terminada devido a "não").
Jens
1
Olá @Brad. Por favor, considere alterar sua resposta aceita para a resposta de nevermind, porque nesse meio tempo, a resposta aceita atualmente ficou um pouco obsoleta.
Bodo Thiesen
@BodoThiesen Done.
Brad,

Respostas:

69

Existem várias maneiras de fazer isso. Os snippets a seguir não produzirão nenhum código sesizeof(someThing) iguais PAGE_SIZE; caso contrário, eles produzirão um erro em tempo de compilação.

1. caminho C11

Começando com C11, você pode usar static_assert(requer #include <assert.h>).

Uso:

static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size");

2. Macro personalizada

Se você apenas deseja obter um erro de tempo de compilação quando sizeof(something)não é o que você esperava, você pode usar a seguinte macro:

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))

Uso:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Este artigo explica em detalhes por que funciona.

3. Específico de MS

No compilador Microsoft C ++, você pode usar a macro C_ASSERT (requer #include <windows.h>), que usa um truque semelhante ao descrito na seção 2.

Uso:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);
deixa pra lá
fonte
4
...isso é insano. Por que essa não é a resposta aceita, @Brad (OP)?
Engenheiro de
Boa referência para BUILD_BUG_ON.
Petr Vepřek
2
A macro não funciona no GNU gcc(testado na versão 4.8.4) (Linux). No ((void)sizeof(...caso de erros com expected identifier or '(' before 'void'e expected ')' before 'sizeof'. Mas, em princípio size_t x = (sizeof(..., funciona como pretendido. Você tem que "usar" o resultado de alguma forma. Para permitir que isso seja chamado várias vezes dentro de uma função ou no escopo global, algo como extern char _BUILD_BUG_ON_ [ (sizeof(...) ];pode ser usado repetidamente (sem efeitos colaterais, não faça referência a _BUILD_BUG_ON_nenhum lugar).
JonBrave
Tenho usado assertivas estáticas por muito mais tempo do que 2011 foi um ano.
Dan
1
@Engineer olha, a insanidade parou;)
Bodo Thiesen
70

Existe alguma maneira de usar um " sizeof" em uma macro de pré-processador?

Não. As diretivas condicionais usam um conjunto restrito de expressões condicionais; sizeofé uma das coisas não permitidas.

As diretivas de pré-processamento são avaliadas antes que a fonte seja analisada (pelo menos conceitualmente), portanto, não há nenhum tipo ou variável para obter seu tamanho.

No entanto, existem técnicas para obter asserções de tempo de compilação em C (por exemplo, consulte esta página ).

James McNellis
fonte
Excelente artigo - solução inteligente! Embora você tenha que administrar - eles realmente forçaram a sintaxe C ao seu limite para fazê-la funcionar! : -O
Brad
1
Acontece que - como o artigo até diz - estou construindo o código do kernel Linux agora - e já existe uma definição no kernel - BUILD_BUG_ON - onde o kernel o usa para coisas como: BUILD_BUG_ON (sizeof (char)! = 8)
Brad
2
@Brad BUILD_BUG_ON e outros gerando código certamente incorreto que não conseguirá compilar (e fornecerá alguma mensagem de erro não óbvia no processo). Na verdade, não é a instrução #if, então você não pode, por exemplo, excluir um bloco de código com base nisso.
keltar
10

Eu sei que é uma resposta tardia, mas para acrescentar à versão de Mike, aqui está uma versão que usamos que não aloca nenhuma memória. Eu não vim com a verificação do tamanho original, encontrei na internet anos atrás e infelizmente não posso citar o autor. Os outros dois são apenas extensões da mesma ideia.

Por serem typedef's, nada é alocado. Com __LINE__ no nome, é sempre um nome diferente para que possa ser copiado e colado quando necessário. Isso funciona em compiladores MS Visual Studio C e compiladores GCC Arm. Não funciona no CodeWarrior, CW reclama da redefinição, não fazendo uso da construção do pré-processador __LINE__.

//Check overall structure size
typedef char p__LINE__[ (sizeof(PARS) == 4184) ? 1 : -1];

//check 8 byte alignment for flash write or similar
typedef char p__LINE__[ ((sizeof(PARS) % 8) == 0) ? 1 : 1];

//check offset in structure to ensure a piece didn't move
typedef char p__LINE__[ (offsetof(PARS, SUB_PARS) == 912) ? 1 : -1];
Paulo
fonte
Isso realmente funciona muito bem para um projeto C padrão ... Eu gosto!
Ashley Duncan
1
Esta deve ser a resposta correta por causa da alocação zero. Melhor ainda para definir:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato
p__LINE__ não produz um nome exclusivo. Ele produz p__LINE__ como uma variável. Você precisaria de uma macro preproc e usar __CONCAT de sys / cdefs.h.
Coroos
9

Eu sei que este tópico é muito antigo, mas ...

Minha solução:

extern char __CHECK__[1/!(<<EXPRESSION THAT SHOULD COME TO ZERO>>)];

Contanto que essa expressão seja igual a zero, ela compila bem. Qualquer outra coisa e explodirá bem ali. Como a variável é externa, ela não ocupará espaço e, desde que ninguém faça referência a ela (o que eles não farão), não causará um erro de link.

Não tão flexível quanto a macro assert, mas não consegui compilar na minha versão do GCC e isso irá ... e acho que irá compilar em qualquer lugar.

Scott
fonte
6
Nunca invente suas próprias macros começando com dois sublinhados. Este caminho é a loucura (também conhecido como comportamento indefinido ).
Jens
vários
portforwardpodcast
não funciona quando compilado com o compilador arm gcc. dá o erro esperado "erro: variável ' CHECK ' modificado no escopo do arquivo"
thunderbird
@Jens Você está certo, mas literalmente não é uma macro, é uma declaração de variável. Claro, isso pode interferir nas macros.
Melebius
4

As respostas existentes mostram apenas como obter o efeito de "afirmações em tempo de compilação" com base no tamanho de um tipo. Isso pode atender às necessidades do OP neste caso específico, mas há outros casos em que você realmente precisa de uma condicional de pré-processador com base no tamanho de um tipo. Veja como fazer:

Escreva para você mesmo um pequeno programa C como:

/* you could call this sizeof_int.c if you like... */
#include <stdio.h>
/* 'int' is just an example, it could be any other type */
int main(void) { printf("%zd", sizeof(int); }

Compile isso. Escreva um script em sua linguagem de script favorita, que executa o programa C acima e captura sua saída. Use essa saída para gerar um arquivo de cabeçalho C. Por exemplo, se você estiver usando Ruby, pode ser parecido com:

sizeof_int = `./sizeof_int`
File.open('include/sizes.h','w') { |f| f.write(<<HEADER) }
/* COMPUTER-GENERATED, DO NOT EDIT BY HAND! */
#define SIZEOF_INT #{sizeof_int}
/* others can go here... */
HEADER

Em seguida, adicione uma regra ao seu Makefile ou outro script de construção, que fará com que ele execute o script acima para construir sizes.h.

Inclua sizes.hsempre que for necessário usar condicionais de pré-processador com base em tamanhos.

Feito!

(Você já digitou ./configure && makepara construir um programa? O que os configurescripts fazem é basicamente igual ao descrito acima ...)

Alex D
fonte
é uma coisa semelhante quando você está usando ferramentas como "autoconf".
Alexander Stohr
4

E a próxima macro:

/* 
 * Simple compile time assertion.
 * Example: CT_ASSERT(sizeof foo <= 16, foo_can_not_exceed_16_bytes);
 */
#define CT_ASSERT(exp, message_identifier) \
    struct compile_time_assertion { \
        char message_identifier : 8 + !(exp); \
    }

Por exemplo, em comentário, o MSVC diz algo como:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits
Sergio
fonte
1
Esta não é uma resposta à pergunta, pois você não pode usar isso em uma #ifdiretiva de pré - processador.
cmaster - restabelecer monica
1

Apenas como referência para esta discussão, relato que alguns compiladores obtêm sizeof () ou tempo de pré-processador.

A resposta do JamesMcNellis está correta, mas alguns compiladores passam por essa limitação (isso provavelmente viola o ansi c estrito).

Nesse caso, refiro-me ao compilador IAR C (provavelmente o líder para microcontrolador profissional / programação embarcada).

Graziano Governatori
fonte
Você tem certeza sobre isso? O IAR afirma que seus compiladores estão em conformidade com os padrões ISO C90 e C99, que não permitem avaliação sizeofno momento do pré-processamento. sizeofdeve ser tratado apenas como um identificador.
Keith Thompson
6
Em 1998, alguém no newsgroup comp.std.c escreveu: "Era bom voltar aos dias em que as coisas #if (sizeof(int) == 8)realmente funcionavam (em alguns compiladores)." A resposta: "Deve ter sido antes do meu tempo.", Foi de Dennis Ritchie.
Keith Thompson
Desculpe pela demora na resposta ... Sim, tenho certeza, tenho exemplos de trabalho de código compilado para microcontroladores 8/16/32 bits, compiladores Renesas (R8 e RX).
graziano governatori
Na verdade, deve haver alguma opção para exigir ISO C "estrito"
graziano governatori
não é uma violação do padrão, desde que o padrão não o proíba. então, eu o chamaria de um recurso raro e fora do padrão - assim, você o evitará em casos regulares para manter a independência do compilador e a portabilidade da plataforma.
Alexander Stohr
1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) pode funcionar


fonte
Esta é uma solução interessante, porém funciona apenas com variáveis ​​definidas, não com tipos. Outra solução que funciona com tipo, mas não com variáveis, seria:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet
7
Não funciona porque você ainda não pode usar seu resultado dentro de uma #ifcondição. Ele não fornece nenhum benefício sizeof(x).
intervalo de
1

Em C11, a _Static_assertpalavra-chave é adicionada. Pode ser usado como:

_Static_assert(sizeof(someThing) == PAGE_SIZE, "Data structure doesn't match page size")
Cagatayo
fonte
0

No meu código c ++ portátil ( http://www.starmessagesoftware.com/cpcclibrary/ ), queria colocar uma guarda segura nos tamanhos de algumas das minhas estruturas ou classes.

Em vez de encontrar uma maneira de o pré-processador lançar um erro (que não pode funcionar com sizeof () conforme declarado aqui), encontrei uma solução aqui que faz com que o compilador emita um erro. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Tive que adaptar esse código para que ele gerasse um erro no meu compilador (xcode):

static union
{
    char   int8_t_incorrect[sizeof(  int8_t) == 1 ? 1: -1];
    char  uint8_t_incorrect[sizeof( uint8_t) == 1 ? 1: -1];
    char  int16_t_incorrect[sizeof( int16_t) == 2 ? 1: -1];
    char uint16_t_incorrect[sizeof(uint16_t) == 2 ? 1: -1];
    char  int32_t_incorrect[sizeof( int32_t) == 4 ? 1: -1];
    char uint32_t_incorrect[sizeof(uint32_t) == 4 ? 1: -1];
};
Mike
fonte
2
Tem certeza de que aqueles “-1” nunca serão interpretados como 0xFFFF… FF, fazendo com que seu programa solicite toda a memória endereçável?
Anton Samsonov
0

Depois de experimentar a macro mencionada, este fragmento parece produzir o resultado desejado ( t.h):

#include <sys/cdefs.h>
#define STATIC_ASSERT(condition) typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
STATIC_ASSERT(sizeof(int) == 4);
STATIC_ASSERT(sizeof(int) == 42);

Em execução cc -E t.h:

# 1 "t.h"
...
# 2 "t.h" 2

typedef char _static_assert_3[ (sizeof(int) == 4) ? 1 : -1];
typedef char _static_assert_4[ (sizeof(int) == 42) ? 1 : -1];

Em execução cc -o t.o t.h:

% cc -o t.o t.h
t.h:4:1: error: '_static_assert_4' declared as an array with a negative size
STATIC_ASSERT(sizeof(int) == 42);
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
t.h:2:84: note: expanded from macro 'STATIC_ASSERT'
  ...typedef char __CONCAT(_static_assert_, __LINE__)[ (condition) ? 1 : -1]
                                                       ^~~~~~~~~~~~~~~~~~~~
1 error generated.

Afinal, 42 não é a resposta para tudo ...

Coroos
fonte
0

Para verificar em tempo de compilação o tamanho das estruturas de dados em relação às suas restrições, usei este truque.

#if defined(__GNUC__)
{ char c1[sizeof(x)-MAX_SIZEOF_X-1]; } // brakets limit c1's scope
#else
{ char c1[sizeof(x)-MAX_SIZEOF_X]; }   
#endif

Se o tamanho de x for maior ou igual ao seu limite MAX_SIZEOF_X, o gcc reclamará com um erro de 'tamanho do array é muito grande'. O VC ++ emitirá o erro C2148 ('o tamanho total da matriz não deve exceder 0x7fffffff bytes') ou C4266 'não pode alocar uma matriz de tamanho constante 0'.

As duas definições são necessárias porque o gcc permitirá que um array de tamanho zero seja definido dessa forma (sizeof x - n).

Miguel de Reyna
fonte
-10

O sizeofoperador não está disponível para o pré-processador, mas você pode transferir sizeofpara o compilador e verificar a condição no tempo de execução:

#define elem_t double

#define compiler_size(x) sizeof(x)

elem_t n;
if (compiler_size(elem_t) == sizeof(int)) {
    printf("%d",(int)n);
} else {
    printf("%lf",(double)n);
}
Freeman
fonte
13
Como isso melhora a resposta já aceita? Qual é o propósito da definição compiler_size? O que seu exemplo tenta mostrar?
ugoren