¿Por qué esta estructura tiene un tamaño de 3 en lugar de 2?

91

He definido esta estructura:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

El sizeof(col)me da la salida de 3, pero no debería ser 2? Si comento solo un elemento, el sizeofes 2. No entiendo por qué: cinco elementos de 3 bits son iguales a 15 bits, y eso es menos de 2 bytes.

¿Existe un "tamaño interno" en la definición de una estructura como ésta? Solo necesito una aclaración, porque de mi noción del idioma hasta ahora, esperaba un tamaño de 2 bytes, no 3.

Raffaello
fuente
4
Probablemente sea una optimización de la alineación. Inicia un nuevo byte, si el siguiente tamaño de bit no encaja en el espacio ocupado real.
πάντα ῥεῖ
4
A menos que tenga algunas restricciones externas que requieran el paquete de bits y su plataforma proporcione algunas garantías adicionales sobre lo que ofrece el estándar, no tiene mucho sentido usar campos de bits.
David Rodríguez - dribeas
3
Tenga en cuenta que para C, usar char es menos portátil que usar int, stackoverflow.com/a/23987436/23118 .
hlovdal
2
Tenga en cuenta que casi todo lo relacionado con los campos de bits está definido por la implementación. Es posible que obtenga diferentes respuestas de diferentes compiladores y no habrá ningún recurso. También tenga en cuenta que debido a que no especificó signed charo unsigned char, no puede decir sin mirar la documentación si el compilador tratará 'llano' charen un campo de bits como firmado o sin firmar, y la decisión podría (en teoría) ser diferente de la decisión sobre si 'plain' charestá firmado o sin firmar cuando se usa fuera de un campo de bits.
Jonathan Leffler
3
En concreto, en el C99, §6.7.2.1 Struct y especificadores de unión, ¶ 4. Un campo de bits tendrá un tipo que es una versión calificado o no calificado de _Bool, signed int, unsigned int, o algún otro tipo definido por la implementación. charPor lo tanto, el uso se incluye en la categoría "otro tipo definido por la implementación".
Jonathan Leffler

Respuestas:

95

Debido a que está utilizando charcomo tipo subyacente para sus campos, el compilador intenta agrupar bits por bytes y, dado que no puede poner más de ocho bits en cada byte, solo puede almacenar dos campos por byte.

La suma total de bits que usa su estructura es 15, por lo que el tamaño ideal para ajustar esa cantidad de datos sería a short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

El código anterior (para una plataforma de 64 bits como la mía) de hecho dará 2como resultado la segunda estructura. Para cualquier cosa mayor que a short, la estructura no llenará más de un elemento del tipo usado, por lo que, para esa misma plataforma, la estructura terminará con un tamaño de cuatro para int, ocho para long, etc.

didierc
fuente
1
La definición de estructura propuesta sigue siendo incorrecta. La definición de estructura correcta usaría 'unsigned short'.
user3629249
21
@ user3629249 ¿Por qué el corto sin firmar es 'correcto'? Si el usuario quiere almacenar de -4 a 3, el corto es correcto. Si el usuario quiere almacenar de 0 a 7, entonces unsigned short es correcto. La pregunta original usaba un tipo de letra firmado, pero no puedo decir si fue intencional o accidental.
Bruce Dawson
2
¿Por qué existe la diferencia entre chary short?
GingerPlusPlus
5
@BruceDawson: El estándar permite que las implementaciones charno estén firmadas…
Thomas Eding
@ThomasEding Es cierto, el estándar permite que char no esté firmado. Pero mi punto principal sigue siendo que no se dio ninguna razón para afirmar que unsigned short fuera correcto (aunque por lo general lo será).
Bruce Dawson
78

Debido a que no puede tener un campo de paquete de bits que se extienda a través del límite de alineación mínimo (que es de 1 byte), probablemente se empaquetarán como

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(los órdenes de campo / relleno dentro del mismo byte no son intencionales, es solo para darle una idea, ya que el compilador podría establecerlos como prefiera)

Jack
fuente
16

Los primeros dos campos de bits encajan en un solo char. El tercero no puede encajar en eso chary necesita uno nuevo. 3 + 3 + 3 = 9 que no encaja en un carácter de 8 bits.

Entonces, el primer par toma a char, el segundo par toma a char, y el último campo de bits obtiene un tercero char.

2501
fuente
15

La mayoría de los compiladores le permiten controlar el relleno, por ejemplo#pragma , usando s . Aquí hay un ejemplo con GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Tenga en cuenta que el comportamiento predeterminado del compilador existe por una razón y probablemente le dará un mejor rendimiento.

Kos
fuente
9

A pesar de que el estándar ANSI C especifica muy poco sobre cómo se empaquetan los campos de bits para ofrecer una ventaja significativa sobre "los compiladores pueden empaquetar campos de bits como mejor les parezca", no obstante, en muchos casos prohíbe a los compiladores empaquetar cosas de la manera más eficiente.

En particular, si una estructura contiene campos de bits, se requiere que un compilador la almacene como una estructura que contenga uno o más campos anónimos de algún tipo de almacenamiento "normal" y luego subdividir lógicamente cada uno de esos campos en sus partes constituyentes del campo de bits. Así, dado:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Si unsigned chares de 8 bits, el compilador deberá asignar cuatro campos de ese tipo y asignar dos campos de bits a todos menos uno (que estaría en un charcampo propio). Si todas las chardeclaraciones se hubieran reemplazado por short, entonces habría dos campos de tipo short, uno de los cuales contendría cinco campos de bits y el otro contendría los dos restantes.

En un procesador sin restricciones de alineación, los datos se podrían distribuir de manera más eficiente utilizando unsigned shortpara los primeros cinco campos y unsigned charpara los dos últimos, almacenando siete campos de tres bits en tres bytes. Si bien debería ser posible almacenar ocho campos de tres bits en tres bytes, un compilador solo podría permitirlo si existiera un tipo numérico de tres bytes que pudiera usarse como el tipo de "campo externo".

Personalmente, considero que los campos de bits definidos son básicamente inútiles. Si el código necesita trabajar con datos empaquetados en binarios, debe definir explícitamente las ubicaciones de almacenamiento de los tipos reales y luego usar macros o algún otro medio similar para acceder a los bits de los mismos. Sería útil si C admitiera una sintaxis como:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Tal sintaxis, si se permite, haría posible que el código use campos de bits de manera portátil, sin tener en cuenta el tamaño de las palabras o el orden de los bytes (foo0 estaría en los tres bits menos significativos de f1, pero esos podrían almacenarse en el dirección más baja o más alta). Sin embargo, a falta de tal característica, las macros son probablemente la única forma portátil de operar con tales cosas.

Super gato
fuente
2
Los diferentes compiladores distribuirán los campos de bits de forma diferente. Escribí cierta documentación sobre cómo lo hace Visual C ++ que puede ser relevante. Señala algunos de los inconvenientes molestos: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson
Bueno, está diciendo un equivalente de almacenar en un tipo normal y usar el operador de campo de bits para lograr la única variable de interés y para simplificar este mecanismo, use alguna macro. Creo que el código generado en c / c ++ también hace algo como esto. El uso de una estructura es solo para una "mejor" organización del código, de hecho no es necesario en absoluto.
Raffaello