Неожиданное поведение с использованием битовых полей и объединений - PullRequest
1 голос
/ 24 марта 2020

Я экспериментировал с битовыми полями и объединениями и создал это:

union REG{
    struct{
        char posX: 7;
        char posY: 7;
        unsigned char dir:  2;
    };

    unsigned short reg;
};

И когда я запускаю sizeof( short ), я получаю 2, но когда я запускаю sizeof( REG ), я получаю 4. Это странно для меня, потому что, когда я суммирую биты, я получаю 7 + 7 + 2 = 16, то есть размер в битах 2-байтового типа данных.

В настоящее время я использую редактор Dev-C ++ с компилятором TDM-GCC 9.9.2 64-bit Debug.

Это мой первый вопрос, поэтому, пожалуйста, скажите мне, если вам нужна дополнительная информация ... Заранее спасибо!

Редактировать: После дальнейших экспериментов я понял, что размер такой же (2 байта), когда я установил размер posX и posY на 6 бит. Но это все еще вызывает недоумение, потому что сумма составляет 14 бит, что составляет менее 2 байтов ...

Редактировать 2: Благодаря AviBerger я понял, что замена типа данных char / unsigned char на short / unsigned short результат '' 'sizeof (REG)' '' превращается в 2. Но я все еще не могу понять «Почему это происходит?»

Ответы [ 3 ]

2 голосов
/ 24 марта 2020

Из спецификации c у нас есть

Реализация может выделить любую адресуемую единицу хранения, достаточно большую для хранения битового поля. Если остается достаточно места, битовое поле, которое непосредственно следует за другим битовым полем в структуре, должно быть упаковано в смежные биты той же единицы. Если остается недостаточно места, определяется, будет ли битовое поле, которое не помещается, в следующий блок или перекрывает соседние блоки, определяется реализацией. Порядок распределения битовых полей внутри блока (от старшего к младшему или от младшего к старшему) определяется реализацией. Выравнивание адресуемой единицы хранения не определено.

Таким образом, фактическое поведение зависит от того, какую единицу выделения размера выбирает компилятор для битовых полей, и позволяет ли он полям охватывать несколько единиц выделения. Этот выбор определяется реализацией, но общей реализацией является использование объявленного типа битового поля в качестве единицы выделения и недопущение пересечения границ единицы выделения. Поэтому, когда вы используете (неоткрытый) тип char, он использует 8-битную единицу выделения. Это означает, что никакие два битовых поля не могут быть объединены в один байт (7 + 7> 8 и 7 + 2> 8), так что в итоге получается 3 единицы выделения (байты), которые затем округляются до 4 для выравнивания, когда в сочетании с коротким значением в объединении.

Когда вы изменяете размер битового поля на 6, теперь второе и третье битовые поля могут помещаться в байте (6 + 2 = 8), поэтому для него требуется только две единицы выделения.

Когда вы изменяете тип битового поля на short, он использует 16-битную единицу выделения, поэтому все 3 битовых поля могут поместиться в одну единицу выделения.

1 голос
/ 24 марта 2020

Взято из стандарта (n4835):

11.4.9 Битовые поля [class.bit]
1 [...] Распределение битовых полей Поля внутри объекта класса определяются реализацией. Выравнивание битовых полей определяется реализацией. Битовые поля упакованы в некоторую адресуемую единицу выделения. [Примечание: битовые поля размещают блоки распределения на некоторых машинах, а не на других. Битовые поля назначаются справа налево на некоторых машинах, слева направо на других. - конец примечания]

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

1 голос
/ 24 марта 2020

При работе с struct и union есть несколько точек утонченности. Наиболее распространенным является то, что поля щедро дополняются для выравнивания по размеру слова процессора.

struct {
     char   c1;
     char   c2;
} s1;

кажется, что это должна быть двухбайтовая структура, но на удивление часто sizeof (s1) будет не 2, а 4 - или даже 8. Так было даже в 1980-х годах с 16-разрядными машинами.

Это потому, что компиляторы C и C ++ будут выравнивать каждый элемент char структуры с двумя байтами или четырьмя граница байта. Мне еще предстоит увидеть элементы структуры, выровненные по 8-байтовой границе, но у нас пока нет нужной 64-битной архитектуры.

Решение состоит в том, чтобы вызвать опцию компиляции для "упаковки структур". Это можно сделать либо в командной строке компилятора, либо включив подходящий параметр #pragma перед объявлением структуры:

#pragma pack(1)   // this particular syntax is valid for many compilers

struct {
     char  c11;
     char  c12;
} s2;

#pragma pack(4)
...