8

为了澄清我的问题,让我们从一个示例程序开始:

#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);
}

可以预见的是,在使用 Visual Studio 2010 的 x86_64 上,会发生以下情况:

*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

等等

暂时忘记可移植性,假设您关心一个 CPU、一个编译器和一个运行时环境。为什么VC++不能把这个结构打包成7个字节?这是一个字长的事情吗?MSDN 文档上说“成员的#pragma pack对齐方式将在 n [在我的情况下为 1] 的倍数或成员大小的倍数的边界上,以较小者为准。” 谁能告诉我为什么我的 sizeof 为 8 而不是 7?

4

5 回答 5

7

MSVC++ 总是至少分配一个与您用于位域的类型相对应的内存单元。您使用unsigned int了 ,这意味着unsigned int最初分配了 a ,并且unsigned int在第一个用完时分配了另一个。没有办法强制 MSVC++ 修剪第二个unsigned int.

基本上,MSVC++ 将您解释unsigned int为表达整个结构的对齐要求的一种方式。

为您的位域(unsigned shortunsigned char)使用较小的类型并重新组合位域,以便它们完全填充分配的单元 - 这样您应该能够尽可能紧密地打包东西。

于 2010-10-12T21:51:14.637 回答
3

位域以您定义的类型存储。由于您使用的是unsigned int, 并且它不适合单个unsigned int,因此编译器必须使用第二个整数并将最后 24 位存储在最后一个整数中。

于 2010-10-12T21:36:46.207 回答
1

好吧,您正在使用 unsigned int 在这种情况下恰好是 32 位。unsigned int 的下一个边界(适合位域)是 64 位 => 8 字节。

于 2010-10-12T21:38:23.987 回答
0

pst 是对的。成员在1 字节边界上对齐(或更小,因为它是一个位域)。整个结构的大小为 8,并在 8 字节边界上对齐。这符合标准和pack选项。文档从不说最后不会有填充。

于 2010-10-12T21:39:54.290 回答
0

为了给出另一个有趣的说明,请考虑您要打包跨越类型边界的结构的情况。例如

struct state {
    unsigned int cost     : 24; 
    unsigned int back     : 21; 
    unsigned int a        :  1; 
    unsigned int b        :  1; 
    unsigned int c        :  1;
};

据我所知,这个结构不能使用 MSVC 打包成 6 个字节。但是,我们可以通过分解前两个字段来获得所需的打包效果:

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;

如果有人知道这样做的更优雅的方式,我很想知道!

于 2010-10-20T21:02:21.697 回答