C compilador afirma – como implementar?

Gostaria de implementar uma “declaração” que evita a compilation, em vez de falhar no tempo de execução, no caso de erro.

Eu atualmente tenho um definido como este, que funciona muito bem, mas que aumenta o tamanho dos binários.

#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;} 

Exemplo de código (que não consegue compilar).

 #define DEFINE_A 1 #define DEFINE_B 1 MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B); 

Como posso implementar isso para que ele não gere nenhum código (para minimizar o tamanho dos binários gerados)?

Uma afirmação em tempo de compilation no padrão puro C é possível, e um pouco de truques pré-processador faz com que seu uso pareça tão limpo quanto o uso em tempo de execução de assert() .

O truque chave é encontrar uma construção que possa ser avaliada em tempo de compilation e possa causar um erro para alguns valores. Uma resposta é a declaração de uma matriz não pode ter um tamanho negativo. Usando um typedef impede a alocação de espaço no sucesso e preserva o erro na falha.

A própria mensagem de erro se referirá criptograficamente à declaração de um tamanho negativo (o GCC diz que “o tamanho do array foo é negativo”), portanto você deve escolher um nome para o tipo de matriz que indique que esse erro realmente é uma verificação de asserção.

Um outro problema a ser tratado é que só é possível digitar um nome de tipo específico uma vez em qualquer unidade de compilation. Portanto, a macro precisa organizar cada uso para que um nome de tipo exclusivo seja declarado.

Minha solução usual tem sido exigir que a macro tenha dois parâmetros. A primeira é a condição para afirmar que é verdadeira e a segunda é parte do nome do tipo declarado nos bastidores. A resposta por plinth sugere o uso de token __LINE__ e a macro predefinida __LINE__ para formar um nome único, possivelmente sem precisar de um argumento extra.

Infelizmente, se a verificação de asserção estiver em um arquivo incluído, ela ainda poderá colidir com uma verificação no mesmo número de linha em um segundo arquivo incluído ou naquele número de linha no arquivo de origem principal. Poderíamos __FILE__ sobre isso usando a macro __FILE__ , mas ela é definida como uma constante de string e não há nenhum truque de pré-processamento que possa transformar uma constante de string em parte de um nome de identificador; sem mencionar que os nomes de arquivos legais podem conter caracteres que não são partes legais de um identificador.

Então, eu proporia o seguinte fragment de código:

 /** A compile time assertion check. * * Validate at compile time that the predicate is true without * generating code. This can be used at any point in a source file * where typedef is legal. * * On success, compilation proceeds normally. * * On failure, attempts to typedef an array type of negative size. The * offending line will look like * typedef assertion_failed_file_h_42[-1] * where file is the content of the second parameter which should * typically be related in some obvious way to the containing file * name, 42 is the line number in the file on which the assertion * appears, and -1 is the result of a calculation based on the * predicate failing. * * \param predicate The predicate to test. It must evaluate to * something that can be coerced to a normal C boolean. * * \param file A sequence of legal identifier characters that should * uniquely identify the source file in which this condition appears. */ #define CASSERT(predicate, file) _impl_CASSERT_LINE(predicate,__LINE__,file) #define _impl_PASTE(a,b) a##b #define _impl_CASSERT_LINE(predicate, line, file) \ typedef char _impl_PASTE(assertion_failed_##file##_,line)[2*!!(predicate)-1]; 

Um uso típico pode ser algo como:

 #include "CAssert.h" ... struct foo { ... /* 76 bytes of members */ }; CASSERT(sizeof(struct foo) == 76, demo_c); 

No GCC, uma falha de declaração seria semelhante a:

 $ gcc -c demo.c
 demo.c: 32: erro: tamanho da matriz `assertion_failed_demo_c_32 'é negativo
 $

A seguinte macro COMPILER_VERIFY(exp) funciona razoavelmente bem.

 // combina argumentos (depois de expandir argumentos)
 #define GLUE (a, b) __GLUE (a, b)
 #define __GLUE (a, b) a ## b

 #define CVERIFY (expr, msg) typedef char GLUE (compiler_verify_, msg) [(expr)?  (+1): (-1)]

 #define COMPILER_VERIFY (exp) CVERIFY (exp, __LINE__)

Ele funciona para C e C ++ e pode ser usado em qualquer lugar que um typedef seja permitido. Se a expressão for verdadeira, ela gerará um typedef para uma matriz de 1 char (que é inofensiva). Se a expressão for falsa, ela gerará um typedef para uma matriz de -1 caracteres, o que geralmente resultará em uma mensagem de erro. A expressão dada como um arugment pode ser qualquer coisa que seja avaliada como uma constante de tempo de compilation (então expressões envolvendo sizeof () funcionam bem). Isso torna muito mais flexível do que

 #if (expr)
 #erro
 #fim se

onde você está restrito a expressões que podem ser avaliadas pelo pré-processador.

O melhor writeup que eu poderia encontrar em asserções estáticas em C é em pixelbeat . Observe que as asserções estáticas estão sendo adicionadas ao C ++ 0X e podem chegar ao C1X, mas isso não ocorrerá por enquanto. Não sei se as macros no link que forneci aumentarão o tamanho de seus binários. Eu suspeitaria que eles não o fariam, pelo menos se você compilar em um nível razoável de otimização, mas sua milhagem pode variar.

Eu sei que você está interessado em C, mas dê uma olhada no C ++ static_assert do boost . (Aliás, isso provavelmente está se tornando disponível em C ++ 1x.)

Nós fizemos algo semelhante, novamente para C ++:

 #define enum COMPILER_ASSERT (expr) {ARG_JOIN (CompilerAssertAtLine, __LINE__) = sizeof (char [(expr)? +1: -1]}}

Isso funciona apenas em C ++, aparentemente. Este artigo descreve uma maneira de modificá-lo para uso em C.

Se o seu compilador definir uma macro de pré-processador como DEBUG ou NDEBUG, você poderá fazer algo assim (caso contrário, você poderia configurá-lo em um Makefile):

 #ifdef DEBUG #define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;} #else #define MY_COMPILER_ASSERT(EXPRESSION) #endif 

Em seguida, seu compilador é ativado apenas para compilações de debugging.

Quando você compilar seus binários finais, defina MY_COMPILER_ASSERT como em branco, para que sua saída não seja incluída no resultado. Defina-o apenas da maneira que você tem para debugging.

Mas, na verdade, você não será capaz de capturar todas as afirmações dessa maneira. Alguns simplesmente não fazem sentido em tempo de compilation (como a afirmação de que um valor não é nulo). Tudo o que você pode fazer é verificar os valores de outros #defines. Eu não tenho certeza porque você quer fazer isso.

Como Leander disse, afirmações estáticas estão sendo adicionadas ao C ++ 11, e agora elas têm.

static_assert(exp, message)

Por exemplo

 #include "myfile.hpp" static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!") void doStuff(MyClass object) { } 

Veja a página da cppreference nela.

Usar ‘#error’ é uma definição de pré-processador válida que faz com que a compilation pare na maioria dos compiladores. Você pode simplesmente fazer assim, por exemplo, para evitar a compilation na debugging:

 #ifdef DEBUG #error Please don't compile now #endif 

Achei isso para dar a mensagem de erro menos confusa para o GCC. Todo o resto tinha algum sufixo sobre um tamanho negativo ou alguma outra coisa confusa:

 #define STATIC_ASSERT(expr, msg) \ typedef char ______Assertion_Failed_____##msg[1]; __unused \ typedef char ______Assertion_Failed_____##msg[(expr)?1:2] __unused 

exemplo de uso:

  unsigned char testvar; STATIC_ASSERT(sizeof(testvar) >= 8, testvar_is_too_small); 

E a mensagem de erro no gcc (Compilador ARM / GNU C: 6.3.1):

 conflicting types for '______Assertion_Failed_____testvar_is_too_small' 

Bem, você poderia usar as static asserts na biblioteca de incentivo .

O que eu acredito que eles fazem lá é definir uma matriz.

  #define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)]; 

Se EXPRESSION for true, define char x[1]; , o que é bom. Se false, define char x[0]; o que é ilegal.