bitlvl
ES

Visualizador de layout de structs C y campos de bits

Pega una o varias definiciones de struct, union, enum o typedef. Cada struct se dibuja byte a byte con su relleno, para la ABI elegida, exactamente como lo dispondrían GCC, Clang o MSVC. Luego pega un volcado hex para ver los valores de los campos.

Decodificar un volcado hex en este struct
Orden de bytes

Los resultados y mensajes de esta herramienta se muestran en inglés.

Por qué los structs tienen huecos

Cada tipo escalar tiene una alineación: en la mayoría de las ABI, un int de 4 bytes debe estar en una dirección divisible por 4, un double o un puntero de 8 bytes en una divisible por 8. El compilador inserta relleno antes de un miembro para alcanzar su alineación, y al final para que los arrays del struct mantengan alineado cada elemento (la alineación del struct es la de su miembro más alineado). Por eso struct { char c; int i; } ocupa 8 bytes, no 5, y struct { char a; long b; char c; } ocupa 24 mientras que { long b; char a; char c; } ocupa 16. Ordenar los miembros de mayor a menor alineación elimina la mayor parte del relleno; eso es justo lo que hace la sugerencia de reordenación.

Qué cambia entre ABIs

Tipox86-64 SysVAArch64i386ARM32MSVC x64
long88444
pointer88448
double8, alineación 88, alineación 88, alineación 48, alineación 88, alineación 8
long longalineación 8alineación 8alineación 4alineación 8alineación 8
long double16, alineación 1616, alineación 1612, alineación 488
wchar_t44442
Reglas de campos de bitsItanium: un campo se coloca en el siguiente bit salvo que cruce una ventana de sizeof(type) bytes; los campos sin nombre no afectan a la alineación (x86) o sí (ARM)cada secuencia de campos del mismo tamaño recibe su propia unidad de almacenamiento; un cambio de tipo empieza una nueva

El mismo código fuente puede dar disposiciones binarias distintas en distintos destinos, por eso los structs que viajan por la red o van a archivos deberían usar tipos de anchura fija, miembros de relleno explícitos y static_assert(sizeof(struct x) == 24).

Campos de bits

Los campos de bits adyacentes se empaquetan en la unidad de almacenamiento de su tipo declarado. En GCC y Clang, un campo va al siguiente bit libre salvo que cruzase una ventana de sizeof(type) bytes; entonces salta al siguiente límite de alineación. Un campo de anchura cero (int :0) obliga al siguiente campo a empezar una nueva unidad. MSVC abre una nueva unidad en cuanto cambia el tamaño del tipo, así que int a:8; char b:8; ocupa 5 bytes en MSVC y 2 en GCC. El orden de los bits dentro de la unidad sigue la endianness del destino (bits menos significativos primero en little-endian), y por eso los campos de bits son una mala forma de describir formatos de cable.

packed, aligned y #pragma pack

__attribute__((packed)) elimina todo el relleno (los miembros pueden quedar desalineados, lo que es lento en x86 y provoca un fallo en algunas configuraciones ARM). #pragma pack(n) limita la alineación de los miembros a n bytes. __attribute__((aligned(n))) y _Alignas(n) aumentan la alineación, normalmente a 16 para SIMD o 64 para una línea de caché. Todo eso se muestra en la disposición.

Preguntas

¿Qué precisión tiene?

El motor se prueba contra clang -fdump-record-layouts para x86-64, i386, AArch64, ARM32 y MSVC x64 en una serie de structs con campos de bits, empaquetado, uniones anidadas y campos de anchura cero. GCC y Clang coinciden en esas ABI. Los casos exóticos (tipos sobrealineados en campos de bits, __int128 en 32 bits, tipos vectoriales) compruébalos con el compilador.

¿Cómo obtengo los valores reales de mi compilador?

clang -Xclang -fdump-record-layouts -c archivo.c imprime cada struct; GCC: compila con -g y ejecuta pahole sobre el objeto, o usa offsetof() y sizeof() en un programa pequeño. Con static_assert(offsetof(struct s, x) == 8) congelas una disposición.

Dice que hay un tipo desconocido.

Los tipos de las cabeceras del sistema (size_t, uint32_t, time_t y la familia DWORD de Windows) están integrados; todo lo demás necesita su typedef encima del struct que lo usa. Un nombre desconocido se trata como int de 4 bytes y se marca.

¿Puede decodificar un struct de un volcado de memoria o un paquete?

Pega los bytes en la sección de decodificación. Enteros, floats, chars, punteros, arrays de char y campos de bits se muestran en el orden de bytes elegido; los structs anidados se listan pero no se despliegan (elige el struct interior para decodificarlo aparte).

¿Por qué la reordenación no ayuda con mi struct?

Si los miembros ya están ordenados por alineación o el struct tiene campos de bits o uniones anónimas (que no se reordenan automáticamente), la sugerencia se oculta. El relleno final es inevitable cuando la alineación del miembro mayor no divide la suma de los tamaños.