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

8 minutos de lectura

avatar de usuario
Raffaello

He definido esta estructura:

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

los sizeof(col) dame la salida de 3, pero ¿no debería ser 2? Si comento solo un elemento, el sizeof es 2. No entiendo por qué: cinco elementos de 3 bits equivalen a 15 bits, y eso es menos de 2 bytes.

¿Hay un “tamaño interno” al definir una estructura como esta? Solo necesito una aclaración, porque desde mi noción del lenguaje hasta ahora, esperaba un tamaño de 2 bytes, no de 3.

  • Probablemente sea la optimización de la alineación. Comienza un nuevo byte, si el siguiente tamaño de bit no cabe en el espacio ocupado real.

    – πάντα ῥεῖ

    2 de noviembre de 2014 a las 13:52

  • A menos que tenga algunas restricciones externas que requieran el empaquetamiento de bits y su plataforma brinde algunas garantías adicionales sobre lo que ofrece el estándar, no tiene mucho sentido usar campos de bits.

    – David Rodríguez – dribeas

    2 de noviembre de 2014 a las 15:35

  • Tenga en cuenta que para C, usar char es menos portátil que usar int, stackoverflow.com/a/23987436/23118.

    – hlovdal

    2 de noviembre de 2014 a las 15:41

  • Tenga en cuenta que casi todo lo relacionado con los campos de bits está definido por la implementación. Puede obtener diferentes respuestas de diferentes compiladores, y no habría recurso. También tenga en cuenta que debido a que no especificó signed char o unsigned charno puede saber sin mirar la documentación si el compilador tratará ‘simple’ char en un campo de bit como firmado o sin firmar, y la decisión podría (en teoría) ser diferente de la decisión sobre si ‘simple’ char está firmado o sin firmar cuando se usa fuera de un campo de bits.

    –Jonathan Leffler

    2 de noviembre de 2014 a las 16:33

  • Específicamente, en C99, §6.7.2.1 Especificadores de estructura y unión, ¶4 Un campo de bits tendrá un tipo que sea una versión calificada o no calificada de _Bool, signed int, unsigned into algún otro tipo definido por la implementación. Usando char por lo tanto, cae en la categoría ‘otro tipo definido por la implementación’.

    –Jonathan Leffler

    2 de noviembre de 2014 a las 16:35

avatar de usuario
didierc

porque estas usando char como tipo subyacente para sus campos, el compilador intenta agrupar bits por bytes, y dado que no puede colocar 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 adaptarse a esa cantidad de datos sería un 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) producirá 2 para la segunda estructura. Para cualquier cosa más grande que un shortla estructura no llenará más de un elemento del tipo usado, por lo que, para esa misma plataforma, la estructura terminará con tamaño cuatro para int ocho para longetc.

  • La definición de estructura propuesta sigue siendo incorrecta. La definición de estructura correcta usaría ‘corto sin firmar’.

    – usuario3629249

    2 de noviembre de 2014 a las 21:21

  • @ user3629249 ¿Por qué el corto sin firmar es ‘correcto’? Si el usuario quiere almacenar de -4 a 3, short es correcto. Si el usuario quiere almacenar de 0 a 7, entonces el corto sin firmar es correcto. La pregunta original usaba un tipo firmado, pero no puedo decir si fue intencional o accidental.

    –Bruce Dawson

    3 de noviembre de 2014 a las 7:14

  • ¿Por qué existe la diferencia entre char y short?

    – JengibrePlusPlus

    3 de noviembre de 2014 a las 13:22

  • @BruceDawson: El estándar permite que las implementaciones tengan char estar sin firmar…

    – Thomas Eding

    4 de noviembre de 2014 a las 2:28

  • @ThomasEding Cierto, el estándar permite que char no esté firmado. Pero mi punto principal permanece, que no se dio ninguna razón para afirmar que unsigned short era correcto (aunque normalmente estarán).

    –Bruce Dawson

    4 de noviembre de 2014 a las 6:28

Debido a que no puede tener un campo de paquete de bits que abarque el límite de alineación mínimo (que es 1 byte), por lo que 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

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

avatar de usuario
2501

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

Así que el primer par toma un charel segundo par toma un chary el último campo de bits obtiene un tercero char.

avatar de usuario
Cos

La mayoría de los compiladores le permiten controlar el relleno, por ejemplo, usando #pragmas. 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 está ahí por una razón y probablemente le dará un mejor rendimiento.

avatar de usuario
Super gato

A pesar de que el estándar ANSI C especifica muy poco acerca de 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”, 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 subdivida lógicamente cada campo en sus partes de campo de bits constituyentes. 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 char es de 8 bits, el compilador tendría que asignar cuatro campos de ese tipo y asignar dos campos de bits a todos menos uno (que estaría en un char campo propio). Me caigo char declaraciones habían sido reemplazadas por shortentonces habría dos campos de tipo shortuno 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 podrían distribuirse de manera más eficiente mediante el uso de unsigned short para los primeros cinco campos y unsigned char para 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, tal como se definen, son básicamente inútiles. Si el código necesita trabajar con datos empaquetados en binario, 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 inferior o superior). Sin embargo, en ausencia de tal característica, las macros son probablemente la única forma portátil de operar con tales cosas.

  • Los diferentes compiladores diseñarán los campos de bits de manera diferente. Escribí alguna documentación sobre cómo lo hace Visual C++ que puede ser relevante. Señala algunas de las trampas molestas: randomascii.wordpress.com/2010/06/06/…

    –Bruce Dawson

    3 de noviembre de 2014 a las 7:15


  • Bueno, está diciendo un equivalente de almacenar en un tipo normal y usar un 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

    3 de noviembre de 2014 a las 15:31


  • Los diferentes compiladores diseñarán los campos de bits de manera diferente. Escribí alguna documentación sobre cómo lo hace Visual C++ que puede ser relevante. Señala algunas de las trampas molestas: randomascii.wordpress.com/2010/06/06/…

    –Bruce Dawson

    3 de noviembre de 2014 a las 7:15


  • Bueno, está diciendo un equivalente de almacenar en un tipo normal y usar un 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

    3 de noviembre de 2014 a las 15:31


¿Ha sido útil esta solución?