Неточный размер структуры с битовыми полями в моей книге
Я изучаю основы языка C. Я пришел к главе структур с битовыми полями. В книге показан пример структуры с двумя различными типами данных: различными bools и различными unsigned ints.
Книга заявляет, что структура имеет размер 16 бит и что без использования прокладки структура будет измерять 10 бит.
Это структура, которую книга использует в примере:
#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;
}
Почему на моем компиляторе вместо этого точно такая же структура измеряет 16 байтов (а не бит) с заполнением и 16 байтов без заполнения?
я использую
GCC (tdm-1) 4.9.2 compiler;
Code::Blocks as IDE.
Windows 7 64 Bit
Intel CPU 64 bit
Это результат, который я получаю:
Вот пример страницы, на которой показан пример:
Ответы
Ответ 1
Microsoft ABI устанавливает битподы по-другому, чем GCC обычно делает это на других платформах. Вы можете использовать Microsoft-совместимый макет с параметром -mms-bitfields
или отключить его с помощью -mno-ms-bitfields
. Вероятно, ваша версия GCC по умолчанию использует -mms-bitfields
.
Согласно документации, когда -mms-bitfields
:
- Каждый объект данных имеет требование выравнивания. Требование выравнивания для всех данных, кроме структур, объединений и массивов, - это либо размер объекта, либо текущий размер упаковки (указанный либо с выровненным атрибутом, либо с пакетом pragma), в зависимости от того, что меньше. Для структур, союзов и массивов требование выравнивания является самым большим требованием к выравниванию его членов. Каждому объекту присваивается смещение так, чтобы: offset% alignment_requirement == 0
- Смежные битовые поля упаковываются в те же 1-, 2- или 4-байтовые единицы выделения, если интегральные типы имеют одинаковый размер, и если следующее поле бит вписывается в текущую единицу выделения без пересечения границы, наложенной общие требования к выравниванию бит-полей.
Поскольку bool
и unsigned int
имеют разные размеры, они упаковываются и выравниваются отдельно, что существенно увеличивает размер структуры. Выравнивание unsigned int
составляет 4 байта, а трижды перестраиваться в середине структуры приводит к общему размеру 16 байт.
Вы можете получить такое же поведение в книге, изменив bool
на unsigned int
или указав -mno-ms-bitfields
(хотя это будет означать, что вы не можете взаимодействовать с кодом, скомпилированным на компиляторах Microsoft).
Обратите внимание, что стандарт C не указывает, как выкладываются битовые поля. Итак, что говорит ваша книга, может быть правдой для некоторых платформ, но не для всех.
Ответ 2
В соответствии с положениями стандарта языка C ваш текст делает необоснованные претензии. В частности, не только стандарт не говорит, что unsigned int
является базовым модулем макета структур любого типа, он явно отказывается от любого требования к размеру блоков хранения, в которых хранятся представления в битовом поле:
Реализация может выделять любой адресный блок хранения, достаточно большой для хранения поля bit-.
(C2011, 6.7.2.1/11)
Текст также содержит предположения о заполнении, которые не поддерживаются стандартом. Внедрение AC может включать произвольное количество дополнений после любых или всех элементов и блоков хранения битполя struct
, включая последнюю. Реализации обычно используют эту свободу для рассмотрения соображений согласования данных, но C не ограничивает дополнение к использованию. Это полностью отделено от безымянных битовых полей, которые ваш текст называет "заполнением".
Я полагаю, что книга заслуживает похвалы, однако, для того, чтобы избежать неприятного общего заблуждения, что C требует, чтобы объявленный тип данных битового поля имел какое-либо отношение к размеру блока (ов) хранения, в котором находится его представление. Стандарт не делает такой связи.
Почему на моем компиляторе вместо этого точно такая же структура измеряет 16 байтов (а не бит) с заполнением и 16 байтов без заполнения?
Чтобы вырезать текст настолько сильно, насколько это возможно, он различает количество бит данных, занятых членами (всего 16 бит, 6, принадлежащих неназванным битовым полям) и общий размер экземпляров struct
. Кажется, что утверждается, что общая структура будет размером unsigned int
, который, по-видимому, составляет 32 бита в описываемой системе, и это будет одинаково для обеих версий структуры.
В принципе, ваши наблюдаемые размеры могут быть объяснены вашей реализацией с использованием 128-битного блока хранения для бит-полей. На практике он, вероятно, использует один или несколько небольших блоков памяти, так что некоторые дополнительные места в каждой структуре относятся к заполнению, предоставляемому приложением, например, я касаюсь выше.
Для реализаций C очень часто накладывается минимальный размер по всем типам структуры и, следовательно, при необходимости формировать представления до такого размера. Часто этот размер соответствует самому строгому требованию к выравниванию любого типа данных, поддерживаемого системой, хотя это опять же рассмотрение внедрения, а не требование языка.
Итог: только полагаясь на детали реализации и/или расширения, вы можете предсказать точный размер struct
, независимо от наличия или отсутствия членов битового поля.
Ответ 3
К моему удивлению, похоже, что разница между некоторыми компиляторами GCC 4.9.2. Во-первых, это мой код:
#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;
}
И теперь, результаты от разных компиляторов.
GCC 4.9.2 от WandBox:
with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits
GCC 4.9.2 из http://cpp.sh/:
with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits
НО
GCC 4.9.2 от theonlinecompiler.com:
with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits
(для правильной компиляции вам нужно chagne %zu
до %u
)
РЕДАКТИРОВАТЬ
@interjay ответ может объяснить это. Когда я добавил -mms-bitfields
в GCC 4.9.2 из WandBox, я получил следующее:
with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits
Ответ 4
В стандарте C не описываются все подробности о том, как переменные должны быть помещены в память. Это оставляет место для оптимизации, которая зависит от используемой платформы.
Чтобы дать себе представление о том, как вещи находятся в памяти, вы можете сделать вот так:
#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;
}
Выполняя это на ideone (https://ideone.com/wbR5tI), я получаю:
4
00000000
01000000
07000000
Поэтому я вижу, что opaque
и fill_color
находятся в первом байте. Выполнение точно такого же кода на компьютере Windows (с использованием gcc) дает:
16
00000000000000000000000000000000
01000000000000000000000000000000
01000000030000000000000000000000
Поэтому здесь я вижу, что opaque
и fill_color
не оба в первом байте. Кажется, что opaque
занимает 4 байта.
Это объясняет, что вы получаете всего 16 байтов, то есть bool
занимает 4 байта, а затем 4 байта для полей между ними и после.
Ответ 5
Прежде чем автор определит структуру, он говорит, что хочет разделить битовые поля на два байта, поэтому будет один байт, содержащий битовые поля для информации, связанной с заполнением, и один байт для связанной с границей информации.
Для этого он добавляет (вставляет) несколько неиспользуемых битов (битполе):
unsigned int 4; // padding of the first byte
он также добавляет второй байт, но в этом нет необходимости.
Таким образом, перед заполнением было бы 10 бит в использовании, и, кроме того, для заполнения было определено 16 бит (но не все из них используются).
Примечание: автор использует bool
для указания поля 1/0. Далее автор предполагает, что тип _Bool
C99 псевдонимом bool
. Но, похоже, компиляторы здесь немного запутались. Замена bool
на unsigned int
решит его. От C99: 6.3.2. Следующие выражения могут использоваться в выражении везде, где может использоваться int или unsigned int:
- Битовое поле типа
_Bool
, int
, signed int
или unsigned int
.
Ответ 6
Вы совершенно неверно истолковываете то, что говорит книга.
Объявлено 16 бит бит бит. 6 бит - это неназванные поля, которые нельзя использовать ни для чего, - что упомянутое дополнение. 16 бит минус 6 бит равно 10 бит. Не считая полей заполнения, структура имеет 10 полезных бит.
Сколько байтов имеет структура, зависит от качества компилятора. По-видимому, вы столкнулись с компилятором, который не упаковывает битовые поля bool в структуре и использует 4 байта для bool, некоторую память для бит-полей, плюс прокладку структуры, всего 4 байта, еще 4 байта для bool, больше памяти для бит-полей, плюс добавление структуры, всего 4 байта, добавляя до 16 байт. Это довольно грустно. Эта структура вполне может быть двух байтов.
Ответ 7
Исторически сложилось два общих способа интерпретации типов элементов битового поля:
-
Проверьте, подписан ли тип или нет, но игнорируйте различия между "char", "short", "int" и т.д. При выборе места для размещения элемента.
-
Если битовому полю предшествует другое с тем же типом или соответствующий тип signed/unsigned, выделите объект такого типа и поместите в него битполе. Поместите следующие битовые поля с тем же типом в этот объект, если они подходят.
Я думаю, что мотивация № 2 заключалась в том, что на платформе, где 16-битные значения должны быть выровнены по словам, компилятор дал что-то вроде:
struct foo {
char x; // Not a bitfield
someType f1 : 1;
someType f2 : 7;
};
может быть в состоянии выделить двухбайтную структуру, причем оба поля помещаются во второй байт, но если структура была:
struct foo {
char x; // Not a bitfield
someType f1 : 1;
someType f2 : 15;
};
было бы необходимо, чтобы все f2
соответствовали одному 16-битовому слову, что, таким образом, потребовало бы байта заполнения перед f1
. Из-за правила Common Initial Sequence f1
должно быть размещено идентично в этих двух структурах, что подразумевает, что если f1
может удовлетворять правилу Common Initial Sequence, ему потребуется заполнить его до его даже в первой структуре.
Как бы то ни было, код, который хочет разрешить более плотную компоновку в первом случае, может сказать:
struct foo {
char x; // Not a bitfield
unsigned char f1 : 1;
unsigned char f2 : 7;
};
и предложите компилятору поместить оба битовых поля в байты сразу после x
. Поскольку тип указан как unsigned char
, компилятору не нужно беспокоиться о возможности 15-битного поля. Если макет был:
struct foo {
char x; // Not a bitfield
unsigned short f1 : 1;
unsigned short f2 : 7;
};
и намерение состояло в том, что f1
и f2
будут размещаться в одном хранилище, тогда компилятору необходимо будет разместить f1 таким образом, чтобы он мог поддерживать выровненный по словам доступ для своего "bunkmate" f2
. Если код был:
struct foo {
char x; // Not a bitfield
unsigned char f1 : 1;
unsigned short f2 : 15;
};
то f1
будет помещаться вслед за x
и f2
в одно слово.
Обратите внимание, что в стандарте C89 добавлен синтаксис, чтобы заставить макет помешать f1
помещаться в байт до использования хранилища 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;
};
Добавление синтаксиса: 0 в C89 во многом исключает необходимость компилятора рассматривать изменяющиеся типы как принудительное выравнивание, за исключением обработки старого кода.