Что делает VC ++ при упаковке BitFields?
-
29-09-2019 - |
Вопрос
Чтобы прояснить мой вопрос, давайте начнем с примера программы:
#include <stdio.h>
#pragma pack(push,1)
struct cc {
unsigned int a : 3;
unsigned int b : 16;
unsigned int c : 1;
unsigned int d : 1;
unsigned int e : 1;
unsigned int f : 1;
unsigned int g : 1;
unsigned int h : 1;
unsigned int i : 6;
unsigned int j : 6;
unsigned int k : 4;
unsigned int l : 15;
};
#pragma pack(pop)
struct cc c;
int main(int argc, char **argv)
{ printf("%d\n",sizeof(c));
}
Вывод «8», что означает, что 56 бит (7 байтов), которые я хочу упаковать, упакованы в 8 байтов, по -видимому, тратясь впустую целый байт. Любопытно, как компилятор выкладывал эти биты в памяти, я попытался написать конкретные значения &c
, например:
int main (int argc, char ** argv)
{
unsigned long long int* pint = &c;
*pint = 0xFFFFFFFF;
printf("c.a = %d", c.a);
...
printf("c.l = %d", c.l);
}
Как и ожидалось, на x86_64 с использованием Visual Studio 2010 происходит следующее:
*pint = 0x00000000 000000FF :
c[0].a = 7
c[0].b = 1
c[0].c = 1
c[0].d = 1
c[0].e = 1
c[0].f = 1
c[0].g = 0
c[0].h = 0
c[0].i = 0
c[0].j = 0
c[0].k = 0
c[0].l = 0
*pint = 0x00000000 0000FF00 :
c[0].a = 0
c[0].b = 0
c[0].c = 0
c[0].d = 0
c[0].e = 0
c[0].f = 0
c[0].g = 1
c[0].h = 127
c[0].i = 0
c[0].j = 0
c[0].k = 0
c[0].l = 0
*pint = 0x00000000 00FF0000 :
c[0].a = 0
c[0].b = 0
c[0].c = 0
c[0].d = 0
c[0].e = 0
c[0].f = 0
c[0].g = 0
c[0].h = 32640
c[0].i = 0
c[0].j = 0
c[0].k = 0
c[0].l = 0
и т. д.
Забудьте на мгновение переносимости и предположим, что вы заботитесь об одном процессоре, один компилятор и одну среду выполнения. Почему VC ++ не может упаковать эту структуру в 7 байт? Это вещь длины слова? То MSDN DOCS на #pragma pack
говорит: «Выравнивание участника будет на границе, которая либо кратна n [1 в моем случае], либо кратно размером члена, в зависимости от того, что меньше». Кто -нибудь может дать мне некоторое представление о том, почему я получаю размер 8, а не 7?
Решение
MSVC ++ всегда выделяет хотя бы единицу памяти, которая соответствует типу, который вы использовали для своего битного поля. Ты использовал unsigned int
, это означает, что unsigned int
первоначально выделяется, а другой unsigned int
выделяется, когда первый измотан. Нет способа заставить MSVC ++ подстричь неиспользованную часть второго unsigned int
.
По сути, MSVC ++ интерпретирует ваш unsigned int
Как способ выразить требования выравнивания для всей структуры.
Используйте меньшие типы для ваших битовых полей (unsigned short
и unsigned char
) и перегруппируйте битовые поля так, чтобы они полностью заполняли выделенную единицу - таким образом, вы должны быть в состоянии упаковать вещи как можно более плотно.
Другие советы
BitFields хранятся в типе, который вы определяете. Поскольку вы используете unsigned int
, и это не поместится ни в одном unsigned int
Затем компилятор должен использовать второе целое число и сохранить последние 24 бита в этом последнем целом.
Ну, вы используете Unsigned Int, который в этом случае составляет 32 бит. Следующая граница (подходящая в Битфилде) для Unsigned int составляет 64 бит => 8 байт.
PST прав. То члены выровнены по 1-байтовым границам (или меньше, так как это немного поля). Общая структура имеет размер 8 и выровнен на 8-байтовой границе. Это соответствует как стандарту, так и pack
вариант. Документы никогда не говорят, что в конце не будет прокладки.
Чтобы дать еще один интересный иллюстрирует, что происходит, рассмотрим случай, когда вы хотите упаковать структуру, которая пересекает границу типа. Например
struct state {
unsigned int cost : 24;
unsigned int back : 21;
unsigned int a : 1;
unsigned int b : 1;
unsigned int c : 1;
};
Эта структура не может быть упакована в 6 байтов, используя MSVC, насколько я знаю. Тем не менее, мы можем получить желаемый эффект упаковки, разбив первые два поля:
struct state_packed {
unsigned short cost_1 : 16;
unsigned char cost_2 : 8;
unsigned short back_1 : 16;
unsigned char back_2 : 5;
unsigned char a : 1;
unsigned char b : 1;
unsigned char c : 1;
};
Это действительно может быть упаковано в 6 байтов. Тем не менее, доступ к первоначальному полю затрат чрезвычайно неловко и уродлив. Один из методов состоит в том, чтобы разыграть указатель State_packed на специализированную фиктивную структуру:
struct state_cost {
unsigned int cost : 24;
unsigned int junk : 8;
};
state_packed sc;
state_packed *p_sc = ≻
sc.a = 1;
(*(struct state_cost *)p_sc).cost = 12345;
sc.b = 1;
Если кто -то знает более элегантный способ сделать это, я бы хотел знать!