Неточный размер структуры с битовыми полями в моей книге - PullRequest
0 голосов
/ 26 апреля 2018

Я изучаю основы языка Си. Я пришел к главе структур с битовыми полями. В книге показан пример структуры с двумя различными типами данных: различные bools и различные unsigned int.

Книга заявляет, что структура имеет размер 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

Вот результат, который я получаю:

enter image description here

Вот изображение страницы, где пример:

enter image description here

Ответы [ 7 ]

0 голосов
/ 26 апреля 2018

Исторически существовало два распространенных способа интерпретации типов элементов битового поля:

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

  2. Если битовому полю не предшествует другое поле того же типа или соответствующий тип со знаком / без знака, выделить объект этого типа и поместите битовое поле внутри него. Поместите следующие битовые поля с тем же введите этот объект, если они подходят.

Я думаю, что мотивация # 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. Из-за правила общей начальной последовательности f1 должно быть одинаково размещено в этих двух структурах, что подразумевает, что если f1 может удовлетворить правило общей начальной последовательности, ему потребуется заполнение перед ним даже в первой структуре.

Как таковой, код, который хочет разрешить более плотную компоновку в первом случае, может сказать:

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

0 голосов
/ 27 апреля 2018

Вы полностью неверно истолковываете то, что говорится в книге.

Объявлено 16 битовых полей. 6 битов - это неназванные поля, которые ни для чего не могут быть использованы - это упомянутый отступ. 16 бит минус 6 бит равняется 10 битам. Не считая полей заполнения, структура имеет 10 полезных битов.

Сколько байтов имеет структура, зависит от качества компилятора. По-видимому, вы столкнулись с компилятором, который не упаковывает битовые поля bool в структуру, и он использует 4 байта для bool, немного памяти для битовых полей, плюс заполнение структуры, всего 4 байта, еще 4 байта для bool, больше памяти для битовых полей плюс добавление структуры, всего 4 байта, добавление до 16 байтов. Это довольно грустно на самом деле. Эта структура вполне может иметь два байта.

0 голосов
/ 26 апреля 2018

Принимая во внимание, как описание положений стандарта языка C, ваш текст предъявляет необоснованные претензии. В частности, стандарт не не только говорит о том, что unsigned int является базовой единицей размещения структур любого вида, он явно отказывается от любых требований к размеру блоков хранения, в которых хранятся представления битовых полей:

Реализация может выделять любую адресуемую единицу памяти большой достаточно для хранения битового поля.

( C2011, 6.7.2.1/11)

В тексте также содержатся допущения относительно заполнения, которые не поддерживаются стандартом. Реализация C свободна включать произвольное количество заполнения после любого или всех элементов и блоков хранения битового поля struct, включая последний. Реализации, как правило, используют эту свободу для учета соображений выравнивания данных, но C не ограничивает заполнение этим использованием. Это в целом отделено от неназванных битовых полей, которые ваш текст называет «padding».

Я полагаю, что книгу следует рекомендовать, однако, чтобы избежать печально распространенного заблуждения о том, что C требует, чтобы объявленный тип данных битового поля имел какое-либо отношение к размеру единицы (единиц) хранения, в которой находится его представление , Стандарт не устанавливает такой ассоциации.

Почему в моем компиляторе точно такая же структура измеряет 16 байтов (а не бит) с заполнением и 16 байтов без заполнения?

Чтобы сократить текст настолько, насколько это возможно, он различает количество бит данных, занятых элементами (всего 16 бит, 6 принадлежащих неназванным битовым полям), и общий размер экземпляров struct. Кажется, утверждается, что общая структура будет иметь размер unsigned int, который, по-видимому, составляет 32 бита в системе, которую она описывает, и это будет одинаково для обеих версий структуры.

В принципе, ваши наблюдаемые размеры могут быть объяснены вашей реализацией, использующей 128-битный блок хранения для битовых полей. На практике он, вероятно, использует один или несколько меньших блоков хранения, так что некоторая часть дополнительного пространства в каждой структуре относится к заполнению, предоставляемому реализацией, как я касался выше.

Для реализаций C очень распространено наложение минимального размера на все типы структур и, следовательно, при необходимости добавление представлений к этому размеру. Часто этот размер соответствует строжайшему требованию выравнивания любого типа данных, поддерживаемого системой, хотя это опять-таки соображение реализации, а не требование языка.

Итог: только полагаясь на детали реализации и / или расширения, вы можете предсказать точный размер struct, независимо от наличия или отсутствия элементов битового поля.

0 голосов
/ 26 апреля 2018

Microsoft ABI размещает битовые поля иначе, чем GCC обычно делает это на других платформах. Вы можете использовать макет, совместимый с Microsoft, с параметром -mms-bitfields или отключить его с помощью -mno-ms-bitfields. Вероятно, что ваша версия GCC использует -mms-bitfields по умолчанию.

Согласно документации, когда -mms-bitfields включен:

  • Каждый объект данных имеет требование выравнивания. Требование выравнивания для всех данных, кроме структур, объединений и массивов, - это либо размер объекта, либо текущий размер упаковки (задается либо с помощью атрибута выравнивания, либо с прагмой упаковки), в зависимости от того, что меньше. Для структур, объединений и массивов требование выравнивания является наибольшим требованием выравнивания его членов. Каждому объекту выделяется смещение, чтобы: смещение% alignment_requirement == 0
  • Смежные битовые поля упаковываются в одну и ту же 1-, 2- или 4-байтовую единицу выделения, если целочисленные типы имеют одинаковый размер и если следующее битовое поле помещается в текущую единицу выделения, не пересекая наложенную границу по общим требованиям выравнивания битовых полей.

Поскольку bool и unsigned int имеют разные размеры, они упакованы и выровнены по отдельности, что существенно увеличивает размер структуры. Выравнивание unsigned int составляет 4 байта, а необходимость трехкратного выравнивания в середине структуры приводит к 16-байтовому общему размеру.

Вы можете получить такое же поведение книги, изменив bool на unsigned int или указав -mno-ms-bitfields (хотя это будет означать, что вы не сможете взаимодействовать с кодом, скомпилированным в компиляторах Microsoft).

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

0 голосов
/ 26 апреля 2018

Стандарт 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 байта для полей между и после.

0 голосов
/ 26 апреля 2018

К моему удивлению, есть разница между некоторыми онлайн-компиляторами 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

(для правильной компиляции нужно изменить %zu до %u)

EDIT

@ interjay's ответ может объяснить это. Когда я добавил -mms-bitfields в GCC 4.9.2 из WandBox, я получил это:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits
0 голосов
/ 26 апреля 2018

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

Для этого он добавляет (вставляет) несколько неиспользуемых битов (битовое поле):

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.
...