Почему не разрешены битовые поля с обычными переменными?

#c #bit-fields

#c #gcc #структура #объединения #битовые поля

Вопрос:

Интересно, почему битовые поля работают с объединениями / структурами, но не с обычной переменной, такой как int или short .
Это работает:

 struct foo {
    int bar : 10;
};
  

Но это не удается:

 int bar : 10; // "Expected ';' at end of declaration"
  

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


Редактировать:

Если бы это было разрешено, вы могли бы создать переменную с 3 байтами, например, без использования элемента struct / union каждый раз. Вот как я бы поступил со структурой:

 struct int24_t {
    int x : 24 __attribute__((packed));
};

struct int24_t var; // sizeof(var) is now 3
// access the value would be easier:
var.x = 123;
  

Комментарии:

1. Потому что это не имеет смысла.

2. @H2CO3 Это не совсем помогает ответить на вопрос. Не хотели бы вы пояснить, почему это имеет смысл в одном случае, но не в другом? (Я уверен, что у вас есть причина сделать свой комментарий, я просто хочу знать, что это такое.)

3. @ap. Конечно. Битовые поля используются для разделения / совместного использования битов между членами структуры. Это неразумно / бессмысленно, когда у вас есть только одна переменная и нет (последовательных) полей (в структуре).

4. @H2CO3 Это имеет смысл. Например, вы могли бы создавать 3-байтовые переменные, не используя имя элемента каждый раз.

5. Я понял, что вопрос OP склоняется к / подразумевается как «почему не разрешено 10-битное значение int?». @Coodey это то, что ты имел в виду?

Ответ №1:

Это субъективный вопрос: «Почему в спецификации так сказано?» Но я попробую.

Переменные в функции обычно имеют «автоматическое» хранение, в отличие от одной из других длительностей (статическая длительность, длительность потока и выделенная длительность).

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

 // sizeof(unsigned) == 4
unsigned x;
  

Это может занимать 4 байта, или 8, или 12, или 0, или оно может быть помещено в три разных регистра одновременно, или в стек и регистр, или оно может занять четыре места в стеке.

Дело в том, что компилятор выполняет выделение для вас. Поскольку вы не выполняете компоновку стека, вам не следует указывать ширину битов.

Расширенное обсуждение: битовые поля на самом деле немного особенные. В спецификации указано, что смежные битовые поля упаковываются в одну и ту же единицу хранения. Битовые поля на самом деле не являются объектами.

  1. Вы не можете sizeof() использовать битовое поле.

  2. Вы не можете malloc() использовать битовое поле.

  3. Вы не можете amp;addressof использовать битовое поле.

Все эти вещи вы можете делать с объектами на C, но не с битовыми полями. Битовые поля — это особая вещь, созданная только для структур и нигде больше.

О int24_t (обновлено): это работает на некоторых архитектурах, но не на других. Это даже немного не переносимо.

 typedef struct {
    int x : 24 __attribute__((packed));
} int24_t;
  

В Linux ELF / x64, OS X / x86, OS X / x64, sizeof(int24_t) == 3 . Но в OS X / PowerPC, sizeof(int24_t) == 4 .

Обратите внимание, что код, который GCC генерирует для загрузки int24_t , в основном эквивалентен этому:

 int result = (((char *) ptr)[0] << 16) |
             (((unsigned char *) ptr)[1] << 8) |
             ((unsigned char *)ptr)[2];
  

Это 9 инструкций на x64, просто для загрузки одного значения.

Комментарии:

1. sizeof(int24_t) будет равен 3

2. @vromanov: Неправильно. Используя приведенное выше объявление, sizeof(int24_t) == 4 , на обычных архитектурах.

3. Пожалуйста, проведите тест! В Centos 6.5 x64 (gcc) sizeof(int24_t)==3

4. @vromanov: Я провел тестирование. sizeof(int24_t) == 4 на GCC 4.0 или 4.2, PowerPC, но 3 на i386 или x64. Он кажется непереносимым (т. Е. Зависит от ABI), даже если компилятор его поддерживает. Не думайте, что это работает только потому, что это работает в вашей системе.

5. PowerPC теперь не является ОБЫЧНОЙ архитектурой 🙂

Ответ №2:

Члены структуры или объединения имеют отношения между их местоположением хранения. Компилятор не может изменить порядок или упаковать их умными способами, чтобы сэкономить место из-за строгих ограничений на компоновку; по сути, единственная свобода, которую компилятор имеет при размещении структур, — это свобода добавлять дополнительные отступы сверх количества, необходимого для выравнивания. Битовые поля позволяют вам вручную предоставить компилятору больше свободы для плотной упаковки информации, обещая, что (1) вам не нужны адреса этих элементов и (2) вам не нужно хранить значения за пределами определенного ограниченного диапазона.

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

Ответ №3:

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

Ответ №4:

Если у вас есть структура QBLOB , которая содержит четыре 2-битных битовых поля в один байт, каждый раз, когда используется эта структура, будет представлять экономию в три байта по сравнению со структурой, которая просто содержит четыре поля типа unsigned char . Если объявить массив QBLOB myArray[1000000] , такой массив займет всего 1 000 000 байт; если бы QBLOB был структурой с четырьмя unsigned char полями, ему потребовалось бы на 3 000 000 байт больше. Таким образом, возможность использования битовых полей может обеспечить большую экономию памяти.

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

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

Ответ №5:

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

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