que - small c compiler
C compilador afirma: ¿cómo implementarlo? (10)
Me gustaría implementar una "afirmación" que impida la compilación, en lugar de fallar en el tiempo de ejecución, en el caso de error.
Actualmente tengo uno definido así, que funciona muy bien, pero que aumenta el tamaño de los binarios.
#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}
Código de muestra (que no se puede compilar).
#define DEFINE_A 1
#define DEFINE_B 1
MY_COMPILER_ASSERT(DEFINE_A == DEFINE_B);
¿Cómo puedo implementar esto para que no genere ningún código (a fin de minimizar el tamaño de los binarios generados)?
Bueno, podrías usar las static_assert .
Lo que creo que hacen allí, es definir una matriz.
#define MY_COMPILER_ASSERT(EXPRESSION) char x[(EXPRESSION)];
Si EXPRESSION es verdadero, define char x[1];
, lo cual está bien Si es falso, define char x[0];
que es ilegal
Como dijo Leander, las aserciones estáticas se están agregando a C ++ 11, y ahora lo tienen.
static_assert(exp, message)
Por ejemplo
#include "myfile.hpp"
static_assert(sizeof(MyClass) == 16, "MyClass is not 16 bytes!")
void doStuff(MyClass object) { }
Vea la página cppreference en él.
Cuando compila sus binarios finales, defina MY_COMPILER_ASSERT para que quede en blanco, de modo que su resultado no se incluya en el resultado. Solo defínalo de la manera que lo tienes para la depuración.
Pero realmente, no podrás ver cada afirmación de esta manera. Algunos simplemente no tienen sentido en el momento de la compilación (como la afirmación de que un valor no es nulo). Todo lo que puede hacer es verificar los valores de otros #defines. No estoy muy seguro de por qué querrías hacer eso.
El uso de ''#error'' es una definición de preprocesador válida que hace que la compilación se detenga en la mayoría de los compiladores. Puede hacerlo así, por ejemplo, para evitar la compilación en depuración:
#ifdef DEBUG
#error Please don''t compile now
#endif
Encontré esto para dar el mensaje de error menos confuso para GCC. Todo lo demás tenía algún sufijo sobre un tamaño negativo o alguna otra cosa confusa:
#define STATIC_ASSERT(expr, msg) /
typedef char ______Assertion_Failed_____##msg[1]; __unused /
typedef char ______Assertion_Failed_____##msg[(expr)?1:2] __unused
uso de ejemplo:
unsigned char testvar;
STATIC_ASSERT(sizeof(testvar) >= 8, testvar_is_too_small);
Y el mensaje de error en gcc (ARM / GNU C Compiler: 6.3.1):
conflicting types for ''______Assertion_Failed_____testvar_is_too_small''
Es posible una afirmación en tiempo de compilación en el estándar C puro, y un poco de engaño de preprocesador hace que su uso se vea tan limpio como el uso en tiempo de ejecución de assert()
.
El truco clave es encontrar un constructo que pueda evaluarse en tiempo de compilación y pueda causar un error en algunos valores. Una respuesta es la declaración de que una matriz no puede tener un tamaño negativo. El uso de un typedef evita la asignación de espacio en caso de éxito y preserva el error en caso de error.
El mensaje de error se referirá crípticamente a la declaración de un tamaño negativo (GCC dice "el tamaño de la matriz foo es negativo"), por lo que debe elegir un nombre para el tipo de matriz que insinúe que este error realmente es una verificación de afirmación.
Un problema adicional para manejar es que solo es posible escribir un nombre de tipo en particular una vez en cualquier unidad de compilación. Por lo tanto, la macro tiene que organizar que cada uso obtenga un nombre de tipo único para declarar.
Mi solución habitual ha sido exigir que la macro tenga dos parámetros. La primera es la condición para afirmar que es verdadera, y la segunda es parte del nombre de tipo declarado detrás de las escenas. La respuesta por zócalo sugiere utilizar pegado de token y la macro predefinida __LINE__
para formar un nombre único posiblemente sin necesidad de un argumento adicional.
Lamentablemente, si la verificación de aserción está en un archivo incluido, aún puede colisionar con una marca en el mismo número de línea en un segundo archivo incluido, o en ese número de línea en el archivo fuente principal. Podríamos __FILE__
utilizando la macro __FILE__
, pero se define como una constante de cadena y no hay ningún truco de preprocesador que pueda convertir una cadena constante en parte de un nombre de identificador; sin mencionar que los nombres de archivos legales pueden contener caracteres que no son partes legales de un identificador.
Entonces, propondría el siguiente fragmento 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];
Un uso típico podría ser algo como:
#include "CAssert.h"
...
struct foo {
... /* 76 bytes of members */
};
CASSERT(sizeof(struct foo) == 76, demo_c);
En GCC, una falla de aserción se vería así:
$ gcc -c demo.c demo.c:32: error: size of array `assertion_failed_demo_c_32'' is negative $
La mejor redacción que pude encontrar en aserciones estáticas en C está en pixelbeat . Tenga en cuenta que las aserciones estáticas se están agregando a C ++ 0X, y pueden llegar a C1X, pero eso no va a ser por un tiempo. No sé si las macros en el enlace que di aumentarán el tamaño de tus binarios. Sospecho que no lo harían, al menos si compilas a un nivel razonable de optimización, pero tu kilometraje puede variar.
La siguiente macro COMPILER_VERIFY(exp)
funciona bastante bien.
// combine arguments (after expanding arguments) #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__)
Funciona tanto para C como para C ++ y se puede usar en cualquier lugar donde se permita un typedef. Si la expresión es verdadera, genera un typedef para una matriz de 1 char (que es inofensivo). Si la expresión es falsa, genera un typedef para una matriz de -1 caracteres, que generalmente dará como resultado un mensaje de error. La expresión dada como un algoritmo puede ser cualquier cosa que se evalúe como una constante en tiempo de compilación (por lo que las expresiones que involucran el tamaño de () funcionan bien). Esto lo hace mucho más flexible que
#if (expr) #error #endif
donde está restringido a expresiones que pueden ser evaluadas por el preprocesador.
Sé que estás interesado en C, pero echa un vistazo a C ++ static_assert . (Por cierto, es probable que esto esté disponible en C ++ 1x).
Hemos hecho algo similar, nuevamente para C ++:
#define COMPILER_ASSERT(expr) enum { ARG_JOIN(CompilerAssertAtLine, __LINE__) = sizeof( char[(expr) ? +1 : -1] ) }
Esto funciona solo en C ++, aparentemente. pixelbeat analiza una forma de modificarlo para su uso en C.
Si su compilador establece una macro de preprocesador como DEBUG o NDEBUG, puede hacer algo como esto (de lo contrario, podría configurar esto en un Makefile):
#ifdef DEBUG
#define MY_COMPILER_ASSERT(EXPRESSION) switch (0) {case 0: case (EXPRESSION):;}
#else
#define MY_COMPILER_ASSERT(EXPRESSION)
#endif
Entonces, su compilador solo afirma para compilaciones de depuración.