C / C ++: принудительный порядок и выравнивание битовых полей

87

Я читал, что порядок битовых полей в структуре зависит от платформы. Что если я использую различные параметры упаковки для конкретного компилятора, будут ли эти данные гарантии храниться в правильном порядке по мере их написания? Например:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

На процессоре Intel с компилятором GCC поля располагались в памяти, как показано. Message.versionбыли первые 3 бита в буфере, а Message.typeзатем следовали. Если я найду эквивалентные варианты упаковки структур для различных компиляторов, будет ли это кроссплатформенный?

Девальд
источник
17
Поскольку буфер - это набор байтов, а не битов, «первые 3 бита в буфере» не совсем точное понятие. Считаете ли вы 3 младших бита первого байта первыми 3 битами или 3 старшими битами?
caf
2
При прохождении по сети «первые 3 бита в буфере» очень хорошо определены.
Джошуа
2
@Joshua IIRC, Ethernet передает младший бит каждого байта первым (поэтому широковещательный бит , где он есть).
тк.
Когда вы говорите «портативный» и «кроссплатформенный», что вы имеете в виду? Исполняемый файл будет правильно обращаться к порядку независимо от целевой ОС - или - код будет компилироваться независимо от набора инструментов?
Гарет Клэборн 08

Ответы:

103

Нет, он не будет полностью портативным. Варианты упаковки структур являются расширениями и сами по себе не являются полностью переносимыми. В дополнение к этому, в параграфе 10 C99 §6.7.2.1 говорится: «Порядок распределения битовых полей в блоке (от высокого к низкому или от низкого к высокому) определяется реализацией».

Даже один компилятор может размещать битовое поле по-разному, например, в зависимости от порядка байтов целевой платформы.

Стивен Кэнон
источник
Да, GCC, например, особо отмечает, что битовые поля упорядочены согласно ABI, а не реализации. Итак, просто использовать один компилятор недостаточно для гарантии упорядочивания. Архитектуру тоже нужно проверить. Это действительно кошмар для портативности.
underscore_d
10
Почему стандарт C не гарантирует порядок битовых полей?
Аарон Кэмпбелл,
7
Трудно последовательно и переносимо определить «порядок» битов в байтах, не говоря уже о порядке битов, которые могут пересекать границы байтов. Любое определение, которое вы выберете, не будет соответствовать значительной части существующей практики.
Стивен Кэнон
2
Определенный в реализации позволяет оптимизировать платформу. На некоторых платформах заполнение между битовыми полями может улучшить доступ, представьте себе четыре семибитных поля в 32-битном int: выравнивание их по каждому 8-му биту является значительным улучшением для платформ, которые имеют чтение байтов.
peterchen
это packedисполнение заказа: stackoverflow.com/questions/1756811/... как обеспечить битовую порядок: stackoverflow.com/questions/6728218/gcc-compiler-bit-order
Чиро Сантилли郝海东冠状病六四事件法轮功
45

Битовые поля сильно различаются от компилятора к компилятору, извините.

С GCC машины с прямым порядком байтов сначала выкладывают биты с обратным порядком байтов, а машины с обратным порядком байтов сначала размещают биты с обратным порядком.

K&R говорит: «Соседние элементы [битового] ​​поля структур упакованы в зависящие от реализации блоки памяти в направлении, зависящем от реализации. Когда поле, следующее за другим полем, не подходит ... оно может быть разделено между блоками или блок может быть padded. Безымянное поле шириной 0 заставляет это заполнение ... "

Следовательно, если вам нужна машинно-независимая двоичная компоновка, вы должны сделать это самостоятельно.

Это последнее утверждение также применимо к небитовым полям из-за заполнения - однако все компиляторы, похоже, имеют какой-то способ принудительной упаковки байтов в структуру, как я вижу, вы уже обнаружили для GCC.

Джошуа
источник
Действительно ли K&R считается полезным справочником, учитывая, что это была предварительная стандартизация и (я полагаю?), Вероятно, была заменена во многих областях?
underscore_d
1
Мой K&R - пост-ANSI.
Джошуа
1
Это смущает: я не знал, что они выпустили ревизию после ANSI. Виноват!
underscore_d
35

Следует избегать битовых полей - они не очень переносимы между компиляторами даже для одной и той же платформы. из стандарта C99 6.7.2.1/10 - «Спецификаторы структуры и объединения» (аналогичная формулировка есть в стандарте C90):

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

Вы не можете гарантировать, будет ли битовое поле "охватывать" границу int или нет, и вы не можете указать, начинается ли битовое поле с нижнего конца int или верхнего конца int (это не зависит от того, является ли процессор прямой или прямой порядок байтов).

Предпочитайте битовые маски. Используйте встроенные строки (или даже макросы) для установки, очистки и тестирования битов.

Майкл Берр
источник
2
Порядок битовых полей можно определить во время компиляции.
Greg A. Woods
9
Кроме того, битовые поля очень предпочтительны при работе с битовыми флагами, которые не имеют внешнего представления вне программы (то есть на диске, в регистрах или в памяти, доступной для других программ, и т. Д.).
Грег А. Вудс
1
@ GregA.Woods: Если это действительно так, пожалуйста, ответьте, как это сделать. Я не смог найти ничего, кроме вашего комментария, когда
поискал в гугле
1
@ GregA.Woods: Извините, мне следовало написать, на какой комментарий я ссылался. Я имел в виду: вы говорите, что «Порядок битовых полей может быть определен во время компиляции». Я ничего не могу об этом и как это сделать.
mozzbozz 05
2
@mozzbozz Загляните на planix.com/~woods/projects/wsg2000.c и найдите определения и использование _BIT_FIELDS_LTOHи_BIT_FIELDS_HTOL
Грег А. Вудс
11

endianness говорят о байтовых порядках, а не о битовых порядках. В настоящее время битовые порядки фиксированы на 99%. Однако при использовании битовых полей следует учитывать порядок байтов. См. Пример ниже.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a
Pierrotlefou
источник
6
Вывод a и b указывает, что порядок байтов все еще говорит о порядках битов И порядках байтов.
Программист Windows,
замечательный пример с порядком битов и проблемами с порядком байтов
Джонатан
1
Вы действительно скомпилировали и запустили код? Значения для «a» и «b» мне не кажутся логичными: вы в основном говорите, что компилятор поменяет местами полубайты в байте из-за порядка байтов. В случае "d" порядок байтов не должен влиять на порядок байтов в массивах символов (при условии, что длина символа 1 байт); если бы компилятор сделал это, мы не смогли бы перебирать массив с помощью указателей. Если, с другой стороны, вы использовали массив из двух 16-битных целых чисел, например: uint16 data [] = {0x1234,0x5678}; тогда d определенно будет 0x7856 в системах с прямым порядком байтов.
Krauss
6

В большинстве случаев, вероятно, но не ставьте на это фарм, потому что, если вы ошибаетесь, вы сильно проиграете.

Если вам действительно, действительно нужно иметь идентичную двоичную информацию, вам нужно будет создать битовые поля с битовыми масками - например, вы используете беззнаковый короткий (16 бит) для сообщения, а затем сделайте такие вещи, как versionMask = 0xE000, чтобы представить три самых верхних бита.

Похожая проблема с выравниванием внутри структур. Например, все процессоры Sparc, PowerPC и 680x0 имеют прямой порядок байтов, и обычно компиляторы Sparc и PowerPC по умолчанию выравнивают элементы структуры по 4-байтовым границам. Однако один компилятор, который я использовал для 680x0, выровнен только по 2-байтовым границам - и не было возможности изменить выравнивание!

Таким образом, для некоторых структур размеры на Sparc и PowerPC идентичны, но меньше на 680x0, а некоторые члены находятся в разных смещениях памяти внутри структуры.

Это была проблема с одним проектом, над которым я работал, потому что серверный процесс, запущенный на Sparc, запрашивал у клиента и обнаруживал, что он был прямым порядком байтов, и предполагал, что он может просто выдавать двоичные структуры в сеть, и клиент может справиться. И это прекрасно работало на клиентах PowerPC и приводило к большим сбоям на клиентах 680x0. Я не писал код, и поиск проблемы занял довольно много времени. Но как только я это сделал, это было легко исправить.

Боб Мерфи
источник
1

Спасибо @BenVoigt за очень полезный комментарий, начинающийся

Нет, они созданы для экономии памяти.

Источник Linux делает использование битового поля для согласования с внешней структурой: /usr/include/linux/ip.h имеет этот код для первого байта в дейтаграммах IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Однако в свете вашего комментария я отказываюсь от попыток заставить это работать для многобайтового битового поля frag_off .

Дункан Роу
источник
-9

Конечно, лучший ответ - использовать класс, который читает / записывает битовые поля в виде потока. Использование структуры битового поля C просто не гарантируется. Не говоря уже о том, что использование этого в реальном кодировании считается непрофессиональным / ленивым / глупым.

99999999
источник
5
Я думаю, неправильно утверждать, что использовать битовые поля глупо, поскольку это обеспечивает очень чистый способ представления аппаратных регистров, которые он был создан для моделирования, на C.
trondd
13
@trondd: Нет, они созданы для экономии памяти. Битовые поля не предназначены для сопоставления с внешними структурами данных, такими как отображенные в память аппаратные регистры, сетевые протоколы или форматы файлов. Если бы они были предназначены для отображения во внешние структуры данных, порядок упаковки был бы стандартизирован.
Ben Voigt
2
Использование битов экономит память. Использование битовых полей увеличивает удобочитаемость. Чем меньше памяти, тем быстрее. Использование битов позволяет выполнять более сложные атомарные операции. В наших приложениях в реальном мире требуется производительность и сложные атомарные операции. Этот ответ не сработает для нас.
johnnycrash
@BenVoigt, вероятно, правда, но если программист захочет подтвердить, что порядок их компилятора / ABI соответствует тому, что им нужно, и соответственно пожертвовать быстрой переносимостью - тогда они, безусловно, могут выполнить эту роль. Что касается 9 *, какая авторитетная масса «реальных программистов» считает любое использование битовых полей «непрофессиональным / ленивым / глупым» и где они это заявили?
underscore_d
2
Использование меньшего объема памяти не всегда быстрее; часто более эффективно использовать больше памяти и сокращать количество операций пост-чтения, а режим процессор / процессор может сделать это еще более верным.
Дэйв Ньютон