¿Cómo puedo utilizar "sizeof" en una macro de preprocesador?

95

¿Hay alguna forma de utilizar una sizeofmacro en un preprocesador?

Por ejemplo, ha habido un montón de situaciones a lo largo de los años en las que quería hacer algo como:

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

Lo que estoy comprobando aquí está completamente inventado; el punto es que a menudo me gusta poner este tipo de comprobaciones en tiempo de compilación (tamaño o alineación) para evitar que alguien modifique una estructura de datos que podría desalinearse o volver a alinearse. cosas de tamaño que las romperían.

No hace falta decir que no parece que pueda usar un sizeofde la manera descrita anteriormente.

Puntilla
fuente
Esta es la razón exacta por la que existen los sistemas de construcción.
Šimon Tóth
3
Esta es la razón exacta por la que las directivas #error siempre deben estar entre comillas dobles (constante de carácter no terminado debido a "does").
Jens
1
Hola @Brad. Considere cambiar su respuesta aceptada a la respuesta de no importa, porque mientras tanto, la respuesta actualmente aceptada se volvió un poco obsoleta.
Bodo Thiesen
@BodoThiesen Hecho.
Brad

Respuestas:

69

Hay varias maneras de hacer esto. Los siguientes fragmentos no producirán código si son sizeof(someThing)iguales PAGE_SIZE; de lo contrario, producirán un error en tiempo de compilación.

1. C11 way

A partir de C11 puede usar static_assert(requiere #include <assert.h>).

Uso:

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

2. Macro personalizada

Si solo desea obtener un error en tiempo de compilación cuando sizeof(something)no es lo que esperaba, puede usar la siguiente macro:

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

Uso:

BUILD_BUG_ON( sizeof(someThing) != PAGE_SIZE );

Este artículo explica en detalle por qué funciona.

3. Específico para EM

En el compilador de Microsoft C ++ puede usar la macro C_ASSERT (requiere #include <windows.h>), que usa un truco similar al descrito en la sección 2.

Uso:

C_ASSERT(sizeof(someThing) == PAGE_SIZE);
no importa
fuente
4
...Eso es una locura. ¿Por qué esta no es la respuesta aceptada, @Brad (OP)?
Ingeniero
Buena referencia a BUILD_BUG_ON.
Petr Vepřek
2
La macro no funciona en GNU gcc(probado en la versión 4.8.4) (Linux). En los ((void)sizeof(...errores de it con expected identifier or '(' before 'void'y expected ')' before 'sizeof'. Pero en principio, en size_t x = (sizeof(...cambio, funciona según lo previsto. Tienes que "usar" el resultado, de alguna manera. Para permitir que esto se llame varias veces, ya sea dentro de una función o en el ámbito global, algo como extern char _BUILD_BUG_ON_ [ (sizeof(...) ];se puede usar repetidamente (sin efectos secundarios, en realidad no hace referencia a _BUILD_BUG_ON_ninguna parte).
JonBrave
He estado usando afirmaciones estáticas durante mucho más tiempo que 2011 ha sido un año.
Dan
1
@Ingeniero mirada, la locura se ha detenido;)
Bodo Thiesen
70

¿Existe alguna forma de utilizar un " sizeof" en una macro de preprocesador?

No. Las directivas condicionales toman un conjunto restringido de expresiones condicionales; sizeofes una de las cosas no permitidas.

Las directivas de preprocesamiento se evalúan antes de que se analice la fuente (al menos conceptualmente), por lo que aún no hay tipos o variables para obtener su tamaño.

Sin embargo, existen técnicas para obtener aserciones en tiempo de compilación en C (por ejemplo, consulte esta página ).

James McNellis
fuente
Gran artículo: ¡solución inteligente! Aunque tienes que administrar, ¡realmente llevaron la sintaxis C al límite para que esto funcione! : -O
Brad
1
Resulta que, como incluso dice el artículo, estoy construyendo el código del kernel de Linux en este momento, y ya hay una definición en el kernel, BUILD_BUG_ON, donde el kernel lo usa para cosas como: BUILD_BUG_ON (sizeof (char)! = 8)
Brad
2
@Brad BUILD_BUG_ON y otros que generan un código seguramente incorrecto que no se compilará (y dará algún mensaje de error no obvio en el proceso). No es realmente una declaración #if, por lo que no puede, por ejemplo, excluir un bloque de código basado en esto.
Keltar
10

Sé que es una respuesta tardía, pero para agregar a la versión de Mike, aquí hay una versión que usamos que no asigna memoria. No se me ocurrió la verificación de tamaño original, lo encontré en Internet hace años y, lamentablemente, no puedo hacer referencia al autor. Los otros dos son solo extensiones de la misma idea.

Debido a que son typedef, no se asigna nada. Con __LINE__ en el nombre, siempre es un nombre diferente para que se pueda copiar y pegar donde sea necesario. Esto funciona en compiladores de MS Visual Studio C y compiladores de GCC Arm. No funciona en CodeWarrior, CW se queja de la redefinición, no hace uso de la construcción del preprocesador __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];
Pablo
fuente
Esto realmente funciona muy bien para un proyecto C estándar ... ¡Me gusta!
Ashley Duncan
1
Esta debería ser la respuesta correcta debido a la asignación cero. Aún mejor en una definición:#define STATIC_ASSERT(condition) typedef char p__LINE__[ (condition) ? 1 : -1];
Renaud Cerrato
p__LINE__ no produce un nombre único. Produce p__LINE__ como variable. Necesitaría una macro preproc y usar __CONCAT de sys / cdefs.h.
Coroos
9

Sé que este hilo es muy antiguo pero ...

Mi solución:

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

Siempre que esa expresión sea igual a cero, se compilará bien. Cualquier otra cosa y explota allí mismo. Debido a que la variable es externa, no ocupará espacio y, siempre que nadie haga referencia a ella (lo que no hará), no causará un error de enlace.

No es tan flexible como la macro de aserción, pero no pude hacer que se compilara en mi versión de GCC y esto lo hará ... y creo que se compilará en casi cualquier lugar.

Scott
fuente
6
Nunca invente sus propias macros comenzando con dos guiones bajos. Este camino se encuentra en la locura (también conocido como comportamiento indefinido ).
Jens
Hay un montón de ejemplos enumerados en esta página pixelbeat.org/programming/gcc/static_assert.html
portforwardpodcast
no funciona cuando se compila con el compilador arm gcc. da el error esperado "error: ' CHECK ' modificado de forma variable en el alcance del archivo"
thunderbird
@Jens Tienes razón, pero esto literalmente no es una macro, es una declaración de variable. Por supuesto, puede interferir con las macros.
Melebius
4

Las respuestas existentes solo muestran cómo lograr el efecto de "aserciones en tiempo de compilación" basadas en el tamaño de un tipo. Eso puede satisfacer las necesidades del OP en este caso particular, pero hay otros casos en los que realmente necesita un preprocesador condicional basado en el tamaño de un tipo. He aquí cómo hacerlo:

Escribe un pequeño programa en 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); }

Compila eso. Escriba un script en su lenguaje de script favorito, que ejecute el programa C anterior y capture su salida. Utilice esa salida para generar un archivo de encabezado C. Por ejemplo, si estuviera usando Ruby, podría verse así:

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

Luego agregue una regla a su Makefile u otro script de compilación, lo que hará que ejecute el script anterior para compilar sizes.h.

Incluya sizes.hsiempre que necesite utilizar condicionales de preprocesador según los tamaños.

¡Hecho!

(¿Alguna vez ha escrito ./configure && makepara crear un programa? Lo que configurehacen los scripts es básicamente como el anterior ...)

Alex D
fuente
es algo similar cuando estás usando herramientas como "autoconf".
Alexander Stohr
4

¿Qué pasa con la siguiente 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 ejemplo, en el comentario MSVC dice algo como:

test.c(42) : error C2034: 'foo_can_not_exceed_16_bytes' : type of bit field too small for number of bits
Sergio
fuente
1
Esta no es una respuesta a la pregunta, ya que no puede usarla en una #ifdirectiva de preprocesador.
cmaster - reinstalar a monica
1

Solo como referencia para esta discusión, informo que algunos compiladores obtienen sizeof () ar tiempo de preprocesador.

La respuesta de JamesMcNellis es correcta, pero algunos compiladores pasan por esta limitación (esto probablemente viola la estricta ansi c).

Como ejemplo de esto, me refiero al compilador C de IAR (probablemente el principal para microcontroladores profesionales / programación integrada).

gobernador graziano
fuente
¿Estás seguro de eso? IAR afirma que sus compiladores se ajustan a las normas ISO C90 y C99, que no permiten la evaluación sizeofen el momento del preprocesamiento. sizeofdebe tratarse solo como un identificador.
Keith Thompson
6
En 1998, alguien del grupo de noticias comp.std.c escribió: "Era agradable en los días en que cosas como #if (sizeof(int) == 8)realmente funcionaban (en algunos compiladores)". La respuesta: "Debe haber sido antes de mi tiempo", fue de Dennis Ritchie.
Keith Thompson
Perdón por la respuesta tardía ... Sí, estoy seguro, tengo ejemplos funcionales de código compilado para microcontroladores de 8/16/32 bits, compiladores Renesas (tanto R8 como RX).
Graziano Governatori
En realidad, debería haber alguna opción para exigir ISO C "estricto"
graziano Governatori
no es una violación del estándar siempre y cuando el estándar no lo prohíba. entonces lo llamaría una característica poco común y no estándar; por lo tanto, la evitará en casos normales para mantener la independencia del compilador y la portabilidad de la plataforma.
Alexander Stohr
1

#define SIZEOF(x) ((char*)(&(x) + 1) - (char*)&(x)) Podría funcionar


fuente
Esta es una solución interesante, sin embargo, solo funciona con variables definidas, no con tipos. Otra solución que funciona con tipo pero no con variables sería:#define SIZEOF_TYPE(x) (((x*)0) + 1)
greydet
7
No funciona porque todavía no puede usar su resultado dentro de una #ifcondición. No proporciona ningún beneficio sobre sizeof(x).
interjay
1

En C11 _Static_assertse agrega la palabra clave. Se puede usar como:

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

En mi código portátil de C ++ ( http://www.starmessagesoftware.com/cpcclibrary/ ) quería poner una guardia segura en los tamaños de algunas de mis estructuras o clases.

En lugar de encontrar una forma para que el preprocesador arroje un error (que no puede funcionar con sizeof () como se indica aquí), encontré una solución aquí que hace que el compilador arroje un error. http://www.barrgroup.com/Embedded-Systems/How-To/C-Fixed-Width-Integers-C99

Tuve que adaptar ese código para que arrojara un error en mi 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];
};
Miguel
fuente
2
¿Está seguro de que esos “−1” nunca se interpretarán como 0xFFFF… FF, lo que provocará que su programa solicite toda la memoria direccionable?
Anton Samsonov
0

Después de probar las macro mencionadas, este fragmento parece producir el resultado deseado ( 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);

Corriendo 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];

Corriendo 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.

42 no es la respuesta a todo después de todo ...

Coroos
fuente
0

Para comprobar en el momento de la compilación el tamaño de las estructuras de datos frente a sus limitaciones, he utilizado este truco.

#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

Si el tamaño de x es mayor o igual que su límite MAX_SIZEOF_X, entonces el gcc se quejará con un error de 'el tamaño de la matriz es demasiado grande'. VC ++ emitirá el error C2148 ('el tamaño total de la matriz no debe exceder 0x7fffffff bytes') o C4266 'no puede asignar una matriz de tamaño constante 0'.

Las dos definiciones son necesarias porque gcc permitirá definir una matriz de tamaño cero de esta manera (tamaño de x - n).

Miguel de Reyna
fuente
-10

El sizeofoperador no está disponible para el preprocesador, pero puede transferirlo sizeofal compilador y verificar la condición en tiempo de ejecución:

#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);
}
Hombre libre
fuente
13
¿Cómo mejora la respuesta ya aceptada? ¿Qué propósito tiene definir compiler_size? ¿Qué intenta mostrar tu ejemplo?
ugoren