Чому цей розмір структури 3 замість 2?


91

Я визначив цю структуру:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

sizeof(col)Дати мені вихід 3, але вона не повинна бути 2? Якщо я коментую лише один елемент, то sizeofце 2. Я не розумію, чому: п'ять елементів з 3 бітів дорівнюють 15 бітам, а це менше 2 байт.

Чи існує "внутрішній розмір" у визначенні такої структури, як ця? Мені просто потрібні роз’яснення, оскільки з мого уявлення про мову досі я очікував розміру 2 байти, а не 3.


4
Ймовірно, це оптимізація вирівнювання. Він починає новий байт, якщо розмір наступного біта не міститься у фактично зайнятому просторі.
πάντα ῥεῖ

4
Якщо у вас відсутні деякі зовнішні обмеження, які вимагають упаковки бітів, і ваша платформа не надає деяких додаткових гарантій щодо того, що пропонує стандарт, мало сенсу використовувати бітові поля.
Девід Родрігес - дріба

3
Зверніть увагу, що для C використання char менше портативне, ніж використання int, stackoverflow.com/a/23987436/23118 .
hlovdal

2
Зверніть увагу, що майже все про бітові поля визначено реалізацією. Ви можете отримати різні відповіді від різних компіляторів, і звернення не буде. Також зауважте, що оскільки ви не вказали signed charабо unsigned char, ви не можете сказати, не подивившись в документацію, чи буде компілятор трактувати "звичайне" charу бітовому полі як підписане чи непідписане, і рішення (теоретично) може відрізнятися від рішення про те, чи 'plain' charпідписується або не підписується, коли використовується поза бітовим полем.
Джонатан Леффлер,

3
В Зокрема, в C99, §6.7.2.1 Struct і об'єднання специфікаторів, ¶4 бітового поле повинно мати тип , який є кваліфікованим або не мають відповідної кваліфікації варіантом _Bool, signed int, unsigned intабо якими - або інших реалізацією певних типів. charОтже, використання входить до категорії „інший тип, визначений реалізацією“.
Джонатан Леффлер,

Відповіді:


95

Оскільки ви використовуєте charяк базовий тип для своїх полів, компілятор намагається згрупувати біти за байтами, і оскільки він не може помістити більше восьми бітів у кожен байт, він може зберігати лише два поля в байті.

Загальна сума бітів, яку використовує ваша структура, становить 15, тому ідеальним розміром для розміщення такої кількості даних буде a short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

Наведений вище код (для 64-розрядної платформи, як моя) справді дасть 2для другої структури. Для всього, що перевищує a short, структура буде заповнювати не більше одного елемента використовуваного типу, тому - для тієї самої платформи структура закінчуватиметься розміром чотири for int, вісім for longі т.д.


1
Запропоноване визначення структури все ще є помилковим. Правильне визначення структури використовувало б "unsigned short".
user3629249 02

21
@ user3629249 Чому непідписаний короткий "правильний"? Якщо користувач хоче зберегти від -4 до 3, тоді короткий правильний. Якщо користувач хоче зберегти від 0 до 7, тоді короткий знак без підпису є правильним. Початкове запитання використовувало підписаний тип, але я не можу сказати, чи це було навмисно чи випадково.
Брюс Доусон,

2
Чому існує різниця між charі short?
GingerPlusPlus

5
@BruceDawson: Стандарт дозволяє реалізаціям charбути без підпису ...
Томас Едінг,

@ThomasEding True, стандарт дійсно дозволяє char без підпису. Але головним моїм твердженням залишається те, що не було вказано жодної причини для твердження, що непідписаний шорт був правильним (хоча зазвичай це і буде).
Брюс Доусон,

78

Оскільки ви не можете мати бітове поле пакета, яке охоплює мінімальну межу вирівнювання (а це 1 байт), тому вони, ймовірно, будуть упаковані як

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(порядки полів / відступів у тому самому байті не є навмисними, це просто для того, щоб дати вам ідею, оскільки компілятор міг би скласти їх, як йому більше подобається)


16

Перші два розрядні поля вміщуються в єдине char. Третій не може вписатися в це charі потребує нового. 3 + 3 + 3 = 9, що не вкладається в 8-бітний символ.

Отже, перша пара приймає a char, друга пара бере a char, а останнє бітове поле отримує третє char.


15

Більшість компіляторів дозволяють контролювати відступ, наприклад, за допомогою #pragmas . Ось приклад із GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Зверніть увагу, що поведінка компілятора за замовчуванням існує з певної причини і, ймовірно, забезпечить вам кращу продуктивність.


9

Незважаючи на те, що стандарт ANSI C вказує занадто мало про те, як упаковуються розрядні поля, щоб надати якусь значну перевагу над "компіляторами дозволяється пакувати розрядні поля, як вони вважають за потрібне", тим не менше, у багатьох випадках компіляторам забороняється пакувати речі найбільш ефективно.

Зокрема, якщо структура містить бітові поля, компілятор повинен зберігати її як структуру, яка містить одне або кілька анонімних полів якогось «нормального» типу зберігання, а потім логічно розподіляти кожне таке поле на складові частини бітового поля. Таким чином, враховуючи:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Якщо unsigned charдорівнює 8 бітам, компілятору потрібно буде виділити чотири поля цього типу та призначити два бітові поля всім, крім одного (яке було б у charвласному полі). Якби всі charдекларації були замінені на short, тоді було б два поля типу short, одне з яких містило б п’ять бітових полів, а друге - решта два.

На процесорі без обмежень по вирівнюванню дані можуть бути викладені більш ефективно, використовуючи unsigned shortдля перших п’яти полів і unsigned charдля останніх двох, зберігаючи сім трибітових полів у трьох байтах. Хоча мала б бути можливість зберігати вісім трибітових полів у трьох байтах, компілятор міг би дозволити це лише за умови існування трибайтового числового типу, який можна було б використовувати як тип "зовнішнього поля".

Особисто я вважаю, що розрядні поля, як вони визначені, в основному марні. Якщо коду потрібно працювати з двійково упакованими даними, він повинен чітко визначити місця зберігання фактичних типів, а потім використовувати макроси або інші подібні засоби для доступу до їх бітів. Було б корисно, якщо б C підтримував такий синтаксис, як:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Такий синтаксис, якщо він дозволений, дасть можливість коду використовувати бітові поля в портативному режимі, не враховуючи розміри слів або впорядкування байтів (foo0 буде в трьох найменш значущих бітах f1, але вони можуть зберігатися в нижча або вища адреса). Однак за відсутності такої функції макроси є, мабуть, єдиним портативним способом роботи з такими речами.


2
Різні компілятори по-різному викладають бітові поля. Я написав деяку документацію про те, як Visual C ++ робить це, що може бути релевантним. Він вказує на деякі надокучливі підводні камені: randomascii.wordpress.com/2010/06/06/…
Брюс Доусон

Ну, ви говорите еквівалент сховища в звичайному типі і використовуєте оператор бітового поля для виконання єдиної цікавої змінної та для спрощення цього механізму використовуйте якийсь макрос. Я думаю, що згенерований код у c / c ++ теж робить щось подібне. Використання структури лише для "кращої" організації коду, насправді зовсім не потрібне.
Раффаелло
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.