type fields example define c gcc struct field bit

fields - c define struct



TamaƱo inexacto de una estructura con campos de bits en mi libro (7)

Antes de que el autor defina la estructura, dice que quiere dividir los campos de bits en dos bytes para que haya un byte que contenga los campos de bits para la información relacionada con el relleno y un byte para la información relacionada con el borde.

Para lograrlo, agrega (inserta) algunos bits no utilizados (campo de bits):

unsigned int 4; // padding of the first byte

También rellena el segundo byte, pero no hay necesidad de eso.

Por lo tanto, antes del relleno, habría 10 bits en uso y después del relleno hay 16 bits definidos (pero no todos están en uso).

Nota: el autor usa bool para indicar un campo de 1/0. El autor luego asume que el tipo _Bool C99 tiene un alias de bool . Pero parece que los compiladores se confunden un poco aquí. Reemplazar bool con unsigned int lo resolvería. Desde C99:

6.3.2: Lo siguiente puede usarse en una expresión donde se puede usar un int o unsigned int:

  • Un campo de bits de tipo _Bool , int , signed int o unsigned int .

Estoy estudiando los fundamentos del lenguaje C. Llegué al capítulo de estructuras con campos de bits. El libro muestra un ejemplo de una estructura con dos tipos diferentes de datos: varios bools y varios ints sin firmar.

El libro declara que la estructura tiene un tamaño de 16 bits y que, sin usar el relleno, la estructura mediría 10 bits.

Esta es la estructura que usa el libro en el ejemplo:

#include <stdio.h> #include <stdbool.h> struct test{ bool opaque : 1; unsigned int fill_color : 3; unsigned int : 4; bool show_border : 1; unsigned int border_color : 3; unsigned int border_style : 2; unsigned int : 2; }; int main(void) { struct test Test; printf("%zu/n", sizeof(Test)); return 0; }

¿Por qué en mi compilador, en cambio, la misma estructura mide 16 bytes (en lugar de bits) con relleno y 16 bytes sin relleno?

Estoy usando

GCC (tdm-1) 4.9.2 compiler; Code::Blocks as IDE. Windows 7 64 Bit Intel CPU 64 bit

Este es el resultado que estoy recibiendo:

Aquí hay una foto de la página donde está el ejemplo:


El Microsoft ABI distribuye los campos de bits de forma diferente a como lo hace GCC en otras plataformas. Puede elegir usar el diseño compatible con Microsoft con la opción -mms-bitfields , o deshabilitarlo con -mno-ms-bitfields . Es probable que su versión de GCC use -mms-bitfields por defecto.

De acuerdo con la documentación, cuando -mms-bitfields está habilitado:

  • Cada objeto de datos tiene un requisito de alineación. El requisito de alineación para todos los datos, excepto estructuras, uniones y matrices, es el tamaño del objeto o el tamaño de empaquetado actual (especificado con el atributo alineado o el paquete pragma), lo que sea menor. Para estructuras, uniones y matrices, el requisito de alineación es el mayor requisito de alineación de sus miembros. A cada objeto se le asigna un desplazamiento de modo que: offset% align_requirement == 0
  • Los campos de bits adyacentes se empaquetan en la misma unidad de asignación de 1, 2 o 4 bytes si los tipos integrales son del mismo tamaño y si el siguiente campo de bits se ajusta a la unidad de asignación actual sin cruzar el límite impuesto por el campo común. Requisitos de alineación de los campos de bits.

Dado que bool y unsigned int tienen diferentes tamaños, se empaquetan y alinean por separado, lo que aumenta sustancialmente el tamaño de la estructura. La alineación de unsigned int es de 4 bytes, y tener que realinear tres veces en medio de la estructura conduce a un tamaño total de 16 bytes.

Puede obtener el mismo comportamiento del libro cambiando bool a unsigned int , o especificando -mno-ms-bitfields (aunque esto significará que no puede interoperar con el código compilado en compiladores de Microsoft).

Tenga en cuenta que el estándar C no especifica cómo se distribuyen los campos de bits. Entonces, lo que dice su libro puede ser cierto para algunas plataformas pero no para todas.


El estándar C no describe todos los detalles sobre cómo se colocarán las variables en la memoria. Esto deja espacio para la optimización que depende de la plataforma utilizada.

Para darse una idea de cómo se ubican las cosas en la memoria, puede hacer lo siguiente:

#include <stdio.h> #include <stdbool.h> struct test{ bool opaque : 1; unsigned int fill_color : 3; unsigned int : 4; bool show_border : 1; unsigned int border_color : 3; unsigned int border_style : 2; unsigned int : 2; }; int main(void) { struct test Test = {0}; int i; printf("%zu/n", sizeof(Test)); unsigned char* p; p = (unsigned char*)&Test; for(i=0; i<sizeof(Test); ++i) { printf("%02x", *p); ++p; } printf("/n"); Test.opaque = true; p = (unsigned char*)&Test; for(i=0; i<sizeof(Test); ++i) { printf("%02x", *p); ++p; } printf("/n"); Test.fill_color = 3; p = (unsigned char*)&Test; for(i=0; i<sizeof(Test); ++i) { printf("%02x", *p); ++p; } printf("/n"); return 0; }

Ejecutando esto en ideone ( https://ideone.com/wbR5tI ) obtengo:

4 00000000 01000000 07000000

Así que puedo ver que opaque y fill_color están en el primer byte. Ejecutar exactamente el mismo código en una máquina con Windows (usando gcc) da:

16 00000000000000000000000000000000 01000000000000000000000000000000 01000000030000000000000000000000

Así que aquí puedo ver que opaque y fill_color no están ambos en el primer byte. Parece que el opaque ocupa 4 bytes.

Esto explica que obtienes 16 bytes en total, es decir, el bool toma 4 bytes cada uno y luego 4 bytes para los campos intermedios y posteriores.


Estás malinterpretando completamente lo que dice el libro.

Hay 16 bits de campos de bits declarados. Los 6 bits son campos sin nombre que no se pueden usar para nada, ese es el relleno mencionado. 16 bits menos 6 bits equivalen a 10 bits. Sin contar los campos de relleno, la estructura tiene 10 bits útiles.

La cantidad de bytes que tiene la estructura depende de la calidad del compilador. Aparentemente, se encontró con un compilador que no empaqueta los campos de bits bool en una estructura, y usa 4 bytes para un bool, algo de memoria para los campos de bits, más el relleno de la estructura, un total de 4 bytes, otros 4 bytes para un bool, más memoria para los campos de bits , además de la estructura de relleno, un total de 4 bytes, sumando hasta 16 bytes. Es bastante triste en realidad. Esta estructura podría ser razonablemente de dos bytes.


Históricamente, ha habido dos formas comunes de interpretar los tipos de elementos de campo de bits:

  1. Examine si el tipo está firmado o sin firmar, pero ignore las distinciones entre "char", "short", "int", etc. para decidir dónde se debe colocar un elemento.

  2. A menos que un campo de bits esté precedido por otro del mismo tipo, o el correspondiente tipo firmado / sin signo, asigne un objeto de ese tipo y coloque el campo de bits dentro de él. Coloque los siguientes campos de bits con el mismo tipo en ese objeto si encajan.

Creo que la motivación detrás del # 2 fue la de una plataforma donde los valores de 16 bits deben estar alineados con las palabras, un compilador dado algo como:

struct foo { char x; // Not a bitfield someType f1 : 1; someType f2 : 7; };

podría ser capaz de asignar una estructura de dos bytes, con ambos campos colocados en el segundo byte, pero si la estructura hubiera sido:

struct foo { char x; // Not a bitfield someType f1 : 1; someType f2 : 15; };

sería necesario que todo f2 encaje dentro de una sola palabra de 16 bits, lo que, por lo tanto, requeriría un byte de relleno antes de f1 . Debido a la regla de la secuencia inicial común, f1 debe colocarse de manera idéntica en esas dos estructuras, lo que implicaría que si f1 pudiera satisfacer la regla de la secuencia inicial común, necesitaría un relleno antes que en la primera estructura.

Tal como está, el código que quiere permitir el diseño más denso en el primer caso puede decir:

struct foo { char x; // Not a bitfield unsigned char f1 : 1; unsigned char f2 : 7; };

e invite al compilador a poner ambos campos de bits en un byte inmediatamente después de x . Dado que el tipo se especifica como un unsigned char , el compilador no necesita preocuparse por la posibilidad de un campo de 15 bits. Si el diseño fuera:

struct foo { char x; // Not a bitfield unsigned short f1 : 1; unsigned short f2 : 7; };

y la intención era que f1 y f2 se ubicaran en el mismo almacenamiento, entonces el compilador tendría que colocar f1 de una manera que pudiera soportar un acceso alineado a la palabra para su "bunkmate" f2 . Si el código fuera:

struct foo { char x; // Not a bitfield unsigned char f1 : 1; unsigned short f2 : 15; };

entonces f1 se colocaría después de x , y f2 en una palabra por sí misma.

Tenga en cuenta que el estándar C89 agregó una sintaxis para forzar el diseño que impide que f1 se coloque en un byte antes de que el almacenamiento use f2 :

struct foo { char x; // Not a bitfield unsigned short : 0; // Forces next bitfield to start at a "short" boundary unsigned short f1 : 1; unsigned short f2 : 15; };

La adición de la sintaxis: 0 en C89 elimina en gran medida la necesidad de que los compiladores consideren los tipos cambiantes como forzantes de alineación, excepto cuando se procesa código antiguo.


Para mi sorpresa, parece haber una diferencia entre algunos compiladores en línea de GCC 4.9.2. Primero, este es mi código:

#include <stdio.h> #include <stdbool.h> struct test { bool opaque : 1; unsigned int fill_color : 3; unsigned int : 4; bool show_border : 1; unsigned int border_color : 3; unsigned int border_style : 2; unsigned int : 2; }; struct test_packed { bool opaque : 1; unsigned int fill_color : 3; unsigned int : 4; bool show_border : 1; unsigned int border_color : 3; unsigned int border_style : 2; unsigned int : 2; } __attribute__((packed)); int main(void) { struct test padding; struct test_packed no_padding; printf("with padding: %zu bytes = %zu bits/n", sizeof(padding), sizeof(padding) * 8); printf("without padding: %zu bytes = %zu bits/n", sizeof(no_padding), sizeof(no_padding) * 8); return 0; }

Y ahora, resultados de diferentes compiladores.

GCC 4.9.2 de WandBox:

with padding: 4 bytes = 32 bits without padding: 2 bytes = 16 bits

GCC 4.9.2 de http://cpp.sh/ :

with padding: 4 bytes = 32 bits without padding: 2 bytes = 16 bits

PERO

GCC 4.9.2 de theonlinecompiler.com:

with padding: 16 bytes = 128 bits without padding: 16 bytes = 128 bits

(para compilar correctamente necesitas chagne %zu a %u )

EDITAR

La answer de @interjay podría explicar esto. Cuando agregué -mms-bitfields a GCC 4.9.2 desde WandBox, obtuve esto:

with padding: 16 bytes = 128 bits without padding: 16 bytes = 128 bits


Tomado como una descripción de las disposiciones de la norma de lenguaje C, su texto hace reclamos injustificados. Específicamente, no solo el estándar no dice que unsigned int es la unidad de diseño básica de estructuras de ningún tipo, sino que explícitamente no tiene ningún requisito sobre el tamaño de las unidades de almacenamiento en las que se almacenan las representaciones de campo de bits:

Una implementación puede asignar cualquier unidad de almacenamiento direccionable lo suficientemente grande como para contener un campo de bits.

( C2011, 6.7.2.1/11 )

El texto también hace suposiciones sobre el relleno que no son compatibles con el estándar. La implementación de CA es libre de incluir una cantidad arbitraria de relleno después de cualquiera o todos los elementos y unidades de almacenamiento de campo de bits de una struct , incluida la última. Las implementaciones suelen utilizar esta libertad para abordar las consideraciones de alineación de datos, pero C no limita el relleno para ese uso. Esto es totalmente independiente de los campos de bits sin nombre a los que su texto se refiere como "relleno".

Sin embargo, creo que el libro debería ser elogiado por evitar el malentendido concepto de que C requiere que el tipo de datos declarado de un campo de bit tenga algo que ver con el tamaño de las unidades de almacenamiento en las que reside su representación. El estándar no hace tal asociación.

¿Por qué en mi compilador, en cambio, la misma estructura mide 16 bytes (en lugar de bits) con relleno y 16 bytes sin relleno?

Para reducir el margen de texto al máximo, distingue entre la cantidad de bits de datos ocupados por miembros (16 bits en total, 6 pertenecientes a campos de bits sin nombre) y el tamaño general de las instancias de la struct . Parece estar afirmando que la estructura general será del tamaño de un unsigned int , que aparentemente tiene 32 bits en el sistema que está describiendo, y que sería el mismo para ambas versiones de la estructura.

En principio, los tamaños observados podrían explicarse por su implementación utilizando una unidad de almacenamiento de 128 bits para los campos de bits. En la práctica, es probable que use una o más unidades de almacenamiento más pequeñas, de modo que parte del espacio adicional en cada estructura sea atribuible al relleno proporcionado por la implementación, como el que mencioné anteriormente.

Es muy común que las implementaciones de C impongan un tamaño mínimo en todos los tipos de estructura y, por lo tanto, rellenen las representaciones a ese tamaño cuando sea necesario. A menudo, este tamaño coincide con el requisito de alineación más estricto de cualquier tipo de datos admitido por el sistema, aunque eso es, nuevamente, una consideración de implementación, no un requisito del lenguaje.

En pocas palabras: solo confiando en los detalles de implementación y / o extensiones, puede predecir el tamaño exacto de una struct , independientemente de la presencia o ausencia de miembros de campo de bits.