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 бітами найвищого порядку?
кафе

2
Під час транзиту в мережі "перші 3 біти в буфері" виявляються дуже чітко визначеними.
Джошуа

2
@Joshua IIRC, Ethernet передає спочатку найменш значущий біт кожного байта (саме тому трансляційний біт знаходиться там, де він є).
тк.

Коли ви говорите "портативний" та "міжплатформенний", що ви маєте на увазі? Виконуваний файл буде правильно отримувати доступ до замовлення незалежно від цільової ОС - або - код буде скомпільований незалежно від ланцюжка інструментів?
Гарет Клаборн

Відповіді:


103

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

Навіть один компілятор може розкласти поле бітів по-різному, наприклад, залежно від спрямованості цільової платформи.


Так, 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 каже: "Сусідні [бітові] члени полів структур упаковуються в блоки, що залежать від реалізації, у напрямку, що залежить від реалізації. Коли поле, яке слідує за іншим полем, не поміщається ... воно може бути розділене між блоками або блок може бути заповнене. Безіменне поле шириною 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: Якщо це справді так, будь ласка, надайте відповідь із описом того, як. Я не міг знайти нічого, крім вашого коментаря, коли гоглював за цим ...
mozzbozz

1
@ GregA.Woods: Вибачте, мав би писати, на який коментар я посилався. Я мав на увазі: Ви кажете, що "Порядок бітових полів можна визначити під час компіляції.". Я нічого не можу про це і як це зробити.
mozzbozz

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

6
Висновки a і b вказують на те, що ендіанс все ще говорить про бітові порядки І байтові порядки.
Програміст для Windows

чудовий приклад із проблематикою впорядкування бітів та впорядкування байтів
Джонатан

1
Ви насправді скомпілювали та запустили код? Значення "a" і "b" мені не здаються логічними: ви в основному говорите, що компілятор мінятиме мінливості в байті через ендіанність. У випадку "d" ендіанни не повинні впливати на порядок байтів у масивах символів (припускаючи, що char має довжину 1 байт); якби компілятор це зробив, ми не змогли б здійснити ітерацію масиву за допомогою покажчиків. Якщо, з іншого боку, ви використовували масив із двох 16-бітових цілих чисел, наприклад: uint16 data [] = {0x1234,0x5678}; тоді d буде точно 0x7856 у маленьких ендіанських системах.
Краус,

6

Найчастіше, напевно, але не робіть ставку на ферму, бо якщо ви помиляєтесь, ви програєте великі.

Якщо вам дійсно, дійсно потрібно мати однакову двійкову інформацію, вам потрібно буде створити бітові поля з бітовими масками - наприклад, ви використовуєте непідписаний шорт (16 біт) для Message, а потім зробите такі речі, як versionMask = 0xE000, щоб представити три найвищі біти.

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

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

Це була проблема з одним проектом, над яким я працював, оскільки серверний процес, що працює на Sparc, запитав би клієнта і виявив, що він є big-endian, і припустив, що він може просто розбризкувати двійкові структури в мережі, і клієнт міг би впоратися. І це добре працювало на клієнтах 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 просто не гарантоване. Не кажучи вже про те, що вважається непрофесійним / ледачим / дурним використовувати це в реальному кодуванні.


5
Я вважаю, що неправильно стверджувати, що нерозумно використовувати бітові поля, оскільки це забезпечує дуже чистий спосіб представити апаратні регістри, які він був створений для моделювання, в C.
trondd

13
@trondd: Ні, вони створені для економії пам’яті. Бітові поля не призначені для зіставлення із зовнішніми структурами даних, такими як відображені в пам'яті апаратні регістри, мережеві протоколи або формати файлів. Якби вони мали на меті відобразити зовнішні структури даних, порядок упаковки був би стандартизованим.
Ben Voigt

2
Використання бітів економить пам’ять. Використання бітових полів збільшує читабельність. Використання менше пам'яті швидше. Використання бітів дозволяє проводити більш складні атомні операції. У вихідних додатках у реальному світі необхідні продуктивність та складні атомні операції. Ця відповідь для нас не спрацює.
johnnycrash

@BenVoigt, ймовірно, правда, але якщо програміст готовий підтвердити, що порядок їх компілятора / ABI відповідає тому, що їм потрібно, і пожертвувати швидкою портативністю відповідно - тоді вони, безумовно, можуть виконати цю роль. Що стосується 9 *, яка авторитетна маса "кодерів реального світу" вважає будь-яке використання бітових полів "непрофесійним / ледачим / дурним" і де вони це заявили?
underscore_d

2
Використання менше пам'яті не завжди швидше; часто ефективніше використовувати більше пам'яті та зменшити кількість операцій після читання, а режим процесора / процесора може зробити це ще більш істинним.
Дейв Ньютон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.