Comportamiento inesperado de campo de bits

Compilé el código,

#include  struct s { int a : 6; _Bool b : 1; _Bool c : 1; _Bool d : 1; _Bool e : 1; _Bool f : 1; _Bool g : 1; int h : 12; }; void main(void) { printf("%d\n", sizeof(struct s)); } 

y la salida fue un poco inesperada.

 12 

Según lo indicado por el proyecto de C11,

… Si queda suficiente espacio, un campo de bits que sigue inmediatamente a otro campo de bits en una estructura se empaquetará en bits adyacentes de la misma unidad …

Esperaba que encajara en 4 bytes ya que usé un comstackdor de 32 bits. Específicamente, utilicé gcc (tdm-1) 5.1.0. ¿Está en contra de la norma?


EDITAR:

Reemplazar todos los _Bool por un int funciona como se espera … No estoy seguro de por qué …


EDITAR:

En gcc 5.4.0, el código funciona como se esperaba. El punto crucial de esta pregunta es por qué los _Bool y el int que se arrastran no encajan en el primero . Creo que no hice muchas suposiciones sobre la implementación (excepto que int es de al menos 4 bytes, lo que es aceptable), y estoy hablando aquí sobre el comportamiento garantizado de C por el estándar de C. Por lo tanto, no puedo estar de acuerdo con algunos comentarios a continuación.

Estos son campos de bits. No hay mucho en el camino de una salida “esperada”, ya que estos están muy mal especificados por la norma. Además, los comstackdores tienden a tener poco apoyo para ellos.

Para empezar, la sección completa que usted cita (6.7.2.1/11) dice:

Una implementación puede asignar cualquier unidad de almacenamiento direccionable lo suficientemente grande como para contener un campo de bits. Si queda suficiente espacio, un campo de bits que sigue inmediatamente a otro campo de bits en una estructura se empaquetará en bits adyacentes de la misma unidad. Si no queda espacio suficiente, si un campo de bits que no se ajusta se coloca en la siguiente unidad o se superpone a las unidades adyacentes se define por la implementación. El orden de asignación de los campos de bits dentro de una unidad (orden alto a orden bajo o orden bajo a orden alto) está definido por la implementación. La alineación de la unidad de almacenamiento direccionable no está especificada.

Todo esto significa que prácticamente no puedes hacer suposiciones sobre cómo los bits terminan en la memoria. No puede saber cómo el comstackdor organizará la alineación, no puede saber el orden de bits de los bits, no puede saber la firmeza, no puede conocer la endianess.

En cuanto a si int y _Bool se fusionarán en la misma “unidad de almacenamiento” … bueno, ¿por qué lo harían? Son tipos incompatibles. El estándar C no menciona lo que sucederá cuando tenga dos campos de bits adyacentes de tipos incompatibles, queda abierto a la interpretación subjetiva. Sospecho que habrá varias preocupaciones de tipo aliasing.

Por lo tanto, es perfectamente correcto que el comstackdor ponga todo esto en “unidades de almacenamiento” separadas. Si coloca elementos del mismo tipo de forma adyacente, sin embargo, espero que se fusionen o que la parte citada no tenga ningún sentido. Por ejemplo, esperaría el tamaño 8 de lo siguiente:

 struct s { int a : 6; int h : 12; _Bool b : 1; _Bool c : 1; _Bool d : 1; _Bool e : 1; _Bool f : 1; _Bool g : 1; }; 

Ahora, lo que debe hacer si desea comportarse de manera determinista, el código portátil es lanzar los campos de bits por la ventana y, en su lugar, utilizar operadores de bit a bit. Son 100% deterministas y portátiles.

Suponiendo que realmente no quieres algunos campos con números misteriosos firmados, sobre los que se refiere tu código original, entonces:

 #define a UINT32_C(0xFC000000) #define b (1u << 18) #define c (1u << 17) #define d (1u << 16) #define e (1u << 15) #define f (1u << 14) #define g (1u << 13) #define h UINT32_C(0x00000FFF) typedef uint32_t special_thing; 

Luego, diseñe funciones de establecimiento / obtención o macros que establezcan / obtengan los datos de este fragmento de 32 bits. Escrito correctamente, incluso puede hacer que dicho código sea independiente del código.

Supongo que el comstackdor está tratando los campos de bits int y los campos de bits _Bool como tipos diferentes. Así que combina todos los _Bool bits _Bool juntos y los campos de bits int juntos, pero no combina los campos de bits _Bool y int . Entonces la estructura se juntaría algo como esto para hacer 12 bytes:

 struct s { int a : 6; // no more int bit-fields, so 4 bytes (usual size) // 6 bits fits in one byte, but these might have to be aligned // on a 4 byte boundary for efficient access, so 4 bytes in total _Bool b : 1; // combine _Bool c : 1; // combine _Bool d : 1; // combine _Bool e : 1; // combine _Bool f : 1; // combine _Bool g : 1; // combine with above _Bool bit-fields to make 6 bits int h : 12; // no more int bit-fields around again, so 4 bytes }; 

EDITAR: Este párrafo del estándar C de 2011 podría interpretarse para hacer que los _Bool bits de _Bool actúen de manera diferente:

6.7.2.1 Especificadores de estructura y unión
Un campo de bits se interpreta como que tiene un tipo entero con signo o sin signo que consiste en el número especificado de bits. Si el valor 0 o 1 se almacena en un campo de bits de ancho distinto de cero de tipo _Bool, el valor del campo de bits se comparará igual al valor almacenado; un campo de bits _Bool tiene la semántica de un _Bool.

Si un _Bool bits _Bool tiene la semántica de un _Bool, quizás el implementador haya interpretado que decir “Actuar como _Bool b : 1; es equivalente a _Bool b;

Una prueba rápida aquí parece confirmar esta teoría.