【问题标题】:What is VC++ doing when packing bitfields?打包位域时 VC++ 在做什么?
【发布时间】:2011-04-24 13:38:17
【问题描述】:

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

#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字节,貌似浪费了一个字节。好奇编译器如何将这些位放在内存中,我尝试将特定值写入&amp;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 docs on #pragma pack 表示“成员的对齐方式将位于 n [在我的情况下为 1] 的倍数或成员大小的倍数的边界上,以较小者为准。”谁能告诉我为什么我的 sizeof 为 8 而不是 7?

【问题讨论】:

  • 文档说“...将在...的边界上”;但是,我找不到关于尺寸保证的任何内容。

标签: c++ visual-c++ bit-fields bit-packing


【解决方案1】:

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

【讨论】:

  • 不,这是错误的。编译器可以以它想要的任何类型存储位域。
  • @Abyx 这与此处的所有其他答案和评论背道而驰。如果您有什么可以支持您的主张,我们很乐意看到。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 问题不在于最后的填充,而在于位域被打包在位域类型的单元内。该结构根本没有填充,只有两个unsigned int 成员。
    • @David,标准说(第 6.7.2.1 节),“位字段被解释为由指定位数组成的有符号或无符号整数类型。[...] 一个实现可以分配任何大到足以容纳位域的可寻址存储单元。”所以我不认为unsigned int 意味着它实际上必须使用unsigned int 作为存储单元,只是一些具有足够位的无符号类型。此外,允许位域跨越存储单元边界。所以我确实认为最后有填充。
    • 顺便说一句,你在看什么标准?当前的 c++ 标准和 C++0x FCD 都没有第 6.7.2.1 节。
    • 标准的那部分至少让我感到困惑。我对该论点的理由是 §9.6/1 包含: 常量表达式 [位数] 可能大于位字段类型的对象表示 (3.9) 中的位数;在这种情况下,额外的位用作填充位,不参与位域的值表示 (3.9)。
    • 这意味着 unsigned char bits : 10 将无法存储 10 位整数(在 8 位/字符机器中),并且将不同于 unsigned short int bits : 10,(假设为 16 位短整数)。由此我推断——我不能指向标准——如果编译器不能提升到更大的表示类型,它可能也不允许下降。在这种特殊情况下,它还必须将表示类型从int 改为short + char(再次假设 8 位字符、16 位短、32 位整数)
    【解决方案4】:

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

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

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

    【讨论】:

    • 在这种情况下,如果他需要 15 位和 16 位,他将无法保存任何东西,他宁愿以至少 9 字节结束,因为他已经使用了 56 位。
    • 不...有了这个建议,我确实可以将它打包成 7 个字节(谢谢 AndreyT)。我不知道你说的 9 个字节是什么意思。
    • @Rooke:请注意,unsigned char bits : 10 是有效的,但可能不是您的意思(最多只能存储 8 位值并保留 2 个额外位用于填充)。 IE。重新组合位域以使每个位域适合分配的单元是至关重要的。
    • @David Rodríguez - dribeas:确实。该解决方案需要重新排序字段。
    【解决方案5】:

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

    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;
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多