Почему этот размер структуры 3 вместо 2?

Я определил эту структуру:

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

sizeof(col) дает мне результат 3, но не должен ли он быть 2? Если я прокомментирую только один элемент, sizeof равно 2. Я не понимаю, почему: пять элементов из 3 бит равны 15 битам и меньше 2 байтов.

Существует ли "внутренний размер" в определении структуры, подобной этой? Мне просто нужно уточнение, потому что из моего представления о языке до сих пор я ожидал размер 2 байта, а не 3.

Ответ 1

Поскольку вы используете char в качестве базового типа для своих полей, компилятор пытается группировать биты по байтам, и поскольку он не может помещать больше восьми бит в каждый байт, он может хранить только два поля на каждый байт.

Общая сумма бит, используемая вашей структурой, равна 15, поэтому идеальный размер для такого большого количества данных будет 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));

}

Вышеприведенный код (для 64-битной платформы, такой как mine) действительно даст 2 для второй структуры. Для чего-либо большего, чем short, структура заполняет не более одного элемента используемого типа, поэтому - для той же платформы - структура будет иметь размер четыре для int, восемь для long и т.д..

Ответ 2

Поскольку у вас не может быть бит поля пакета, который охватывает границу минимального выравнивания (которая составляет 1 байт), поэтому они, вероятно, будут упакованы как

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

(порядки поля/заполнения внутри одного и того же байта не являются преднамеренными, это просто для того, чтобы дать вам идею, поскольку компилятор мог бы отложить их, как это предпочитает)

Ответ 3

Первые два битовых поля вписываются в один char. Третий не может вписаться в это char и нуждается в новом. 3 + 3 + 3 = 9, который не вписывается в 8 бит char.

Итак, первая пара принимает char, вторая пара принимает char, а последнее поле бит получает третий char.

Ответ 4

Большинство компиляторов позволяют вам управлять дополнением, например. используя #pragma s. Вот пример с 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
}

Обратите внимание, что поведение по умолчанию для компилятора по умолчанию существует и, вероятно, даст вам лучшую производительность.

Ответ 5

Несмотря на то, что в стандарте ANSI C указано слишком мало о том, как битовые поля упаковываются, чтобы предложить какое-либо существенное преимущество перед "компиляторам разрешено упаковывать битовые поля, но они сочтут нужным", тем не менее он во многих случаях запрещает компиляторам упаковывать вещи в наиболее эффективные мода.

В частности, если структура содержит битовые поля, компилятор должен хранить ее как структуру, которая содержит одно или несколько анонимных полей некоторого "нормального" типа хранилища, а затем логически подразделяет каждое такое поле на его составные части битового поля. Таким образом, учитывая:

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;

Если unsigned char - 8 бит, компилятор должен будет выделить четыре поля этого типа и назначить два битовых поля всем, кроме одного (которое было бы в собственном поле char). Если бы все объявления char были заменены на short, тогда было бы два поля типа short, один из которых будет содержать пять битовых полей, а другой из них будет содержать оставшиеся два.

На процессоре без ограничений выравнивания данные могут быть более эффективны с помощью unsigned short для первых пяти полей и unsigned char для последних двух, сохраняя семь трехбитовых полей в трех байтах. В то время как должно быть возможно хранить восемь трехбитовых полей в трех байтах, компилятор мог разрешить это только в том случае, если существовал трехбайтовый числовой тип, который можно было использовать как тип "внешнего поля".

Лично я рассматриваю битовые поля как определено как бесполезные. Если код должен работать с двоично упакованными данными, он должен явно определять места хранения фактических типов, а затем использовать макросы или некоторые другие подобные средства для доступа к их битам. Было бы полезно, если бы C поддерживал такой синтаксис, как:

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;

Такой синтаксис, если это разрешено, позволит коду использовать бит-поля переносимым образом, независимо от размеров слов или порядков байтов (foo0 будет в трех наименее значимых битах f1, но это может быть хранятся на более низком или более высоком адресе). Однако при отсутствии такой функции макросы, вероятно, являются единственным переносным способом работы с такими вещами.