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

91

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

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.

Рафаэлло
источник
4
Вероятно, это оптимизация выравнивания. Он начинает новый байт, если размер следующего бита не помещается в фактически занимаемое пространство.
πάντα ῥεῖ 02
4
Если у вас нет каких-либо внешних ограничений, требующих упаковки битов, и ваша платформа предоставляет некоторые дополнительные гарантии по сравнению с тем, что предлагает стандарт, в использовании битовых полей нет особого смысла.
Дэвид Родригес - dribeas
3
Обратите внимание, что для C использование char менее переносимо, чем использование int, stackoverflow.com/a/23987436/23118 .
hlovdal 02
2
Обратите внимание, что почти все, что касается битовых полей, определяется реализацией. Вы можете получить разные ответы от разных компиляторов, и к этому не будет никакого выхода. Также обратите внимание, что, поскольку вы не указали signed charили unsigned char, вы не можете сказать, не просматривая документацию, будет ли компилятор рассматривать `` простой '' charв битовом поле как подписанный или неподписанный, и решение может (теоретически) отличаться от решения о том, будет ли 'plain' charявляется знаковым или беззнаковым при использовании вне битового поля.
Джонатан Леффлер
3
В частности, в C99, §6.7.2.1 Struct и объединение спецификаторов, ¶4 битового поле должно иметь тип , который является квалифицированным или неквалифицированным вариантом _Bool, signed int, unsigned intили какими - либо других реализацией определенных типов. Использование charпоэтому попадает в категорию «другие реализации определенных типа».
Джонатан Леффлер

Ответы:

95

Поскольку вы используете 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-битной платформы, такой как моя) действительно даст результат 2для второй структуры. Для всего, что больше a short, структура будет заполнять не более одного элемента используемого типа, поэтому - для той же платформы - структура будет иметь размер четыре для int, восемь для longи т. Д.

Didierc
источник
1
Предлагаемое определение структуры все еще неверно. Правильное определение структуры будет использовать «беззнаковое короткое».
user3629249 02
21
@ user3629249 Почему короткое беззнаковое слово «правильно»? Если пользователь хочет сохранить от -4 до 3, тогда короткое значение будет правильным. Если пользователь хочет сохранить от 0 до 7, то короткое замыкание без знака является правильным. В исходном вопросе использовался подписанный тип, но я не могу сказать, было ли это намеренным или случайным.
Брюс Доусон
2
Почему есть разница между charи short?
GingerPlusPlus
5
@BruceDawson: Стандарт разрешает реализациям charбез подписи…
Томас Эдинг
@ThomasEding Верно, стандарт не разрешает char быть без знака. Но моя основная мысль остается в том, что не было дано никаких оснований утверждать, что unsigned short было правильным (хотя обычно так и бывает ).
Брюс Доусон
78

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

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

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

Джек
источник
16

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

Таким образом, первая пара принимает a char, вторая пара принимает a char, а последнее битовое поле получает третье char.

2501
источник
15

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

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

Кос
источник
9

Несмотря на то, что стандарт 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, но они могут храниться в нижний или верхний адрес). Однако при отсутствии такой функции макросы, вероятно, являются единственным переносимым способом работы с такими вещами.

суперкар
источник
2
Разные компиляторы размещают битовые поля по-разному. Я написал некоторую документацию о том, как это делает Visual C ++, которая может быть актуальной. Он указывает на некоторые досадные ловушки: randomascii.wordpress.com/2010/06/06/…
Брюс Доусон
Что ж, вы говорите эквивалент store в нормальном типе и используете оператор битового поля для выполнения единственной интересующей переменной и для упрощения этого механизма используйте некоторый макрос. Я думаю, что сгенерированный код на c / c ++ тоже делает что-то подобное. Использование структуры просто для «лучшей» организации кода, на самом деле совсем не обязательно.
Raffaello