2010-10-12 2 views
6

Чтобы прояснить мой вопрос, давайте начнем с примера программы:Что делает VC++ при упаковке битполей?

#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 основной (интермедиат ARGC, символ ** 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 байт? Это вещь длиной в слово? В на #pragma pack говорится, что «выравнивание элемента будет на границе, либо кратном n [1 в моем случае], либо кратной размеру члена, в зависимости от того, что меньше». Может ли кто-нибудь дать мне некоторое представление о том, почему я получаю sizeof 8, а не 7?

+2

В документации говорится: «... будет на границе, которая ...»; однако я не могу найти, где он говорит что-либо о гарантии размера. – 2010-10-12 21:38:40

ответ

5

MSVC++ всегда выделяет как минимум единицу памяти, соответствующую типу, который использовался для вашего битового поля. Вы использовали unsigned int, а это значит, что изначально выделяется unsigned int, а еще один unsigned int выделяется, когда первый из них исчерпан. Нельзя заставить MSVC++ обрезать неиспользуемую часть второго unsigned int.

В основном, MSVC++ интерпретирует ваш unsigned int как способ выражения требований к выравниванию для всей структуры.

Используйте меньшие типы для ваших битовых полей (unsigned short и unsigned char) и перегруппируйте битовые поля так, чтобы они полностью заполнили выделенный блок - таким образом вы должны быть в состоянии упаковать вещи как можно более плотно.

+0

В этом случае он ничего не может спасти, если ему нужны 15 и 16 бит, он скорее всего получит не менее 9 байт, потому что он уже использует 56 бит. – Vinzenz

+0

Нет ... С помощью этого совета я могу действительно упаковать его в 7 байт (спасибо AndreyT). Я понятия не имею, что вы имеете в виду около 9 байтов. – Rooke

+0

@Rooke: Обратите внимание, что 'unsigned char bits: 10' действительно, но, вероятно, не то, что вы имеете в виду (будет хранить только до 8-битных значений и зарезервировать 2 дополнительных бита для заполнения). То есть крайне важно перегруппировать бит-поля, чтобы каждое битовое поле соответствовало выделенной единице. –

3

Битвые поля хранятся в том типе, который вы определяете. Поскольку вы используете unsigned int, и он не будет вписываться ни в один unsigned int, тогда компилятор должен использовать второе целое число и хранить последние 24 бита в этом последнем целом.

+0

нет, это не так. компилятор может хранить бит-поля любого желаемого типа. – Abyx

+0

@Abyx это противоречит любому другому ответу и комментарию здесь. Если у вас есть что-то, чтобы поддержать ваше утверждение, мы бы хотели его увидеть. – Spencer

1

Ну, вы используете unsigned int, который в этом случае будет 32 бит. Следующая граница (для вставки в битовое поле) для unsigned int равна 64 бит => 8 байтов.

0

pst - правый. Члены выровнены по 1-байтовым границам (или меньше, так как это битполе). Общая структура имеет размер 8 и выравнивается по 8-байтовой границе. Это соответствует как стандарту, так и варианту pack. Документы никогда не говорят, что в конце не будет отступов.

+0

Проблема не в дополнении в конце, а в том, что битовые поля упакованы внутри единиц типа битпотока. Структура вообще не имеет прописных букв, только два члена 'unsigned int'. –

+0

@David, стандарт говорит (§6.7.2.1): «Битовое поле интерпретируется как целочисленный тип с подписью или без знака, состоящий из определенного количества битов. [...] Реализация может выделять любой адресный блок хранения достаточно большой, чтобы провести бит- . Поэтому я не думаю, что 'unsigned int' означает, что на самом деле он должен использовать' unsigned int' в качестве единицы хранения, а только некоторый неподписанный тип с достаточным количеством бит. Кроме того, бит-полям разрешено пересекать границы единицы хранения. Поэтому я думаю, что в конце есть дополнение. –

+0

BTW, какой стандарт вы ищете? Ни текущий стандарт C++, ни CCD 0x FCD не имеют раздела 6.7.2.1. –

0

Чтобы дать еще один интересный пример того, что происходит, рассмотрим случай, когда вы хотите упаковать структуру, пересекающую границу типа. Например.

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; 

sc.a = 1; 
(*(struct state_cost *)p_sc).cost = 12345; 
sc.b = 1; 

Если кто-нибудь знает более изящный способ сделать это, я хотел бы знать!

Смежные вопросы