Dada una estructura, por ejemplo:
struct A { char a; char b; } __attribute__((packed)); Quiero que el desplazamiento de b (en este ejemplo, 1) en la estructura se imprima en tiempo de compilación. No quiero tener que ejecutar el programa y llamar a algo como printf("%zu", offsetof(struct A, b)); porque la impresión no es trivial en mi plataforma. Quiero que el propio compilador imprima el desplazamiento, algo así como:
> gcc main.c The offset of b is 1 Probé algunos enfoques usando #pragma message y offsetof , siendo mi más cercano:
#define OFFSET offsetof(struct A, b) #define XSTR(x) STR(x) #define STR(x) #x #pragma message "Offset: " XSTR(OFFSET)que solo imprime:
> gcc main.c main.c:12:9: note: #pragma message: Offset: __builtin_offsetof (struct A, b) que no imprime el desplazamiento numérico. Es posible realizar una búsqueda binaria del desplazamiento en el momento de la compilación usando _Static_assert , pero mis estructuras reales son grandes y esto puede volverse un poco engorroso.
Dada esta macro:
#define PRINT_OFFSETOF(A, B) char (*__daniel_kleinstein_is_cool)[sizeof(char[offsetof(A, B)])] = 1 Úselo en su función main() (o cualquier función):
struct Test { char x; long long y; int z; }; int main(void) { PRINT_OFFSETOF(struct Test, z); return 0; }Y obtendrás esta advertencia:
warning: initialization of 'char (*)[16]' from 'int' makes pointer from integer without a cast [-Wint-conversion] Por lo tanto, offsetof(struct Test, z) == 16 .
NOTA: en caso de que offsetof() devuelva 0 (por ejemplo: PRINT_OFFSETOF(struct Test, x) ), la advertencia del compilador tendrá char (*)[] en lugar de char (*)[16] .
NOTA 2: Solo probé esto con GCC.
Una forma posible es hacer que el desplazamiento sea del tamaño de una matriz y luego pasar la dirección de esa matriz a una función que espera un puntero incompatible para que imprima el tipo:
static int a[offsetof(struct A, b)]; static void foo1(int *p) { (void)p; } static void foo2(void) { foo1(&a); }Esto imprime:
x1.c: In function 'foo2': x1.c:13:1: warning: passing argument 1 of 'foo1' from incompatible pointer type [enabled by default] static void foo2(void) { foo1(&a); } ^ x1.c:12:13: note: expected 'int *' but argument is of type 'int (*)[1]' static void foo1(int *p) { (void)p; } ^Tal vez sería aceptable un nuevo paso de preprocesamiento. Esto podría hacerse como un paso separado que no afectará su producción binaria.
offsetdumper.sh
#!/bin/bash # # pre-process some source file(s), add a macro + main() and a file with rules # describing the interesting symbos. Compile and run the result. dumprulefile="$1" shift # Define your own macros, like OFFSET, in the "Here Document" below: { gcc -E "$@" && cat<<EOF #define OFFSET(x,y) do { printf("%s::%s %zu\n", #x, #y, offsetof(x,y)); } while(0) #include <stddef.h> #include <stdio.h> int main() { EOF cat "$dumprulefile" echo '}' } | g++ -xc - && ./a.out rules
OFFSET(A,a); OFFSET(A,b); source.h
typedef struct { char a; char b; } __attribute__((packed)) A;Ejemplo:
$ ./offsetdumper.sh rules *.h A::a 0 A::b 1 Esto es un poco frágil y no funcionará si su source.h incluye una función main , por lo que puede necesitar algunos retoques para satisfacer sus necesidades.
Sorprendentemente, parece que __builtin_choose_expr funciona dentro del atributo de función __deprecated__ . El siguiente programa:
#include <stddef.h> struct A { char a; char b; } __attribute__((packed)); #define printval_case(x, xstr, y, ...) __builtin_choose_expr(x == y, xstr"="#y, __VA_ARGS__) #define printval(x) do { \ __attribute__((__deprecated__( \ printval_case(x, #x, 0, \ printval_case(x, #x, 1, \ printval_case(x, #x, 2, \ printval_case(x, #x, 3, \ /* etc... */ \ (void)0 )))) \ ))) void printval() {} \ printval(); \ } while (0) int main() { printval(offsetof(struct A, a)); printval(offsetof(struct A, b)); }Cuando se compila, entonces gcc generará:
<source>:23:30: warning: 'printval' is deprecated: offsetof(struct A, a)=0 [-Wdeprecated-declarations] <source>:24:30: warning: 'printval' is deprecated: offsetof(struct A, b)=1 [-Wdeprecated-declarations]De manera similar, podría incrustar el valor en el ejecutable (de manera similar a cómo CMake detecta cosas del compilador ):
#include <stddef.h> struct A { char a; char b; } __attribute__((packed)); #define printval_case(x, xstr, y, ...) __builtin_choose_expr(x == y, xstr"="#y, __VA_ARGS__) #define embedval(x) do { \ static const __attribute__((__used__)) const char unused[] = \ printval_case(x, #x, 0, \ printval_case(x, #x, 1, \ printval_case(x, #x, 2, \ printval_case(x, #x, 3, \ /* etc... */ \ (void)0 )))); \ } while (0) int main() { embedval(offsetof(struct A, a)); embedval(offsetof(struct A, b)); }luego:
$ gcc file.c && strings ./a.out | grep offsetof offsetof(struct A, b)=1 offsetof(struct A, a)=0Sospecho que la restricción establecida "Quiero que el compilador imprima el desplazamiento" es un problema XY y que simplemente necesitamos que las herramientas de compilación impriman el desplazamiento en el sistema utilizado para compilar, no específicamente el compilador.
En este caso, GCC y Clang tienen la capacidad de incluir texto arbitrario en su salida de ensamblaje e incluir varios operandos de datos en ese texto, incluidos valores inmediatos para compensaciones de estructura.
Dentro de cualquier función, incluya estas líneas:
#if GenerateStructureOffsets __asm__("# offsetof(struct A, b) = %c0" : : "i" (offsetof(struct A, b))); #endif Luego compila con los modificadores -DGenerateStructureOffsets y -S . El compilador generará un archivo llamado SourceFileName .s , y puede usar -o Name para darle un nombre diferente si lo desea.
Luego, grep "## offsetof" Name encontrará esta línea, mostrando algo como:
## compensación de (estructura A, b) = 1
Luego puede usar sed u otras herramientas para extraer el valor.
En el __asm__ , "i" dice que se genere un operando "inmediato". El (offsetof(struct A, b)) que sigue que da el valor que debería tener. En la primera cadena entre comillas, %c0 se reemplaza con el valor de ese operando.
El 0 indica qué operando reemplazar; si hubo más de uno enumerado más adelante en el __asm__ , se numeran 0, 1, 2, 3, etc. (También hay un mecanismo para nombrarlos en lugar de numerarlos, que no se muestra aquí). Normalmente, %0 se reemplazaría por la forma de operando inmediato adecuado para el lenguaje ensamblador de destino, como $1 o #1 . Sin embargo, el modificador c dice que use la constante simple, por lo que el texto de reemplazo es solo el valor, en este caso 1 .