Что делает VC ++ при упаковке битовых полей? - PullRequest
7 голосов
/ 13 октября 2010

Чтобы прояснить мой вопрос, давайте начнем с примера программы:

#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?

Ответы [ 5 ]

6 голосов
/ 13 октября 2010

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

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

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

3 голосов
/ 13 октября 2010

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

1 голос
/ 13 октября 2010

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

0 голосов
/ 21 октября 2010

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

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;

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

0 голосов
/ 13 октября 2010

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

...