代码之家  ›  专栏  ›  技术社区  ›  Adam Holmberg

C++数据成员对齐与数组打包

  •  15
  • Adam Holmberg  · 技术社区  · 15 年前

    在代码审查期间,我遇到了一些定义简单结构的代码,如下所示:

    class foo {
       unsigned char a;
       unsigned char b;
       unsigned char c;
    }
    

    在其他地方,将定义这些对象的数组:

    foo listOfFoos[SOME_NUM];
    

    稍后,将结构原始复制到缓冲区中:

    memcpy(pBuff,listOfFoos,3*SOME_NUM);
    

    这段代码依赖于这样的假设:a.)foo的大小为3,并且不应用填充;b.)这些对象的数组在它们之间没有填充。

    我在两个平台(Redhat64b、Solaris9)上用GNU进行了测试,两个平台都能工作。

    上述假设是否有效?如果没有,在什么条件下(如操作系统/编译器的变化)它们可能会失败?

    9 回复  |  直到 10 年前
        1
  •  18
  •   Jerry Coffin    15 年前

    对象数组必须是连续的,因此对象之间永远不会有填充,尽管填充可以添加到对象的末尾(产生几乎相同的效果)。

    考虑到你正在使用char,这些假设很可能是正确的,但C++标准当然不能保证它。不同的编译器,甚至只是传递给当前编译器的标志的更改,都可能导致填充被插入到结构的元素之间或结构的最后一个元素之后,或者两者都插入。

        2
  •  20
  •   Peter Mortensen Mohit    12 年前

    这样做绝对更安全:

    sizeof(foo) * SOME_NUM
    
        3
  •  5
  •   nschmidt    15 年前

    如果您像这样复制数组,应该使用

    memcpy(pBuff,listOfFoos,sizeof(listOfFoos));
    

    只要您将pbuff分配到相同的大小,这将始终有效。 这样,您就完全不需要假设填充和对齐。

    大多数编译器将一个结构或类与包含的最大类型所需的对齐方式对齐。在字符的情况下,这意味着没有对齐和填充,但是如果您添加一个短字符,例如,您的类将是6个字节大,在最后一个字符和短字符之间添加一个字节的填充。

        4
  •  4
  •   NawaMan    15 年前

    我认为这样做的原因是因为结构中的所有字段都是char,它们对齐一个字段。如果至少有一个字段未对齐1,则结构/类的对齐方式将不是1(对齐方式将取决于字段顺序和对齐方式)。

    让我们看一些例子:

    #include <stdio.h>
    #include <stddef.h>
    
    typedef struct {
        unsigned char a;
        unsigned char b;
        unsigned char c;
    } Foo;
    typedef struct {
        unsigned short i;
        unsigned char  a;
        unsigned char  b;
        unsigned char  c;
    } Bar;
    typedef struct { Foo F[5]; } F_B;
    typedef struct { Bar B[5]; } B_F;
    
    
    #define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )
    
    int main(void) {
        printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
        printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
        printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
        printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
    }
    

    执行时,结果是:

    Foo:: Size: 3; Alignment: 1
    Bar:: Size: 6; Alignment: 2
    F_B:: Size: 15; Alignment: 1
    B_F:: Size: 30; Alignment: 2
    

    你可以看到那个条和f_b有对齐方式2,所以它的字段i将正确对齐。你也可以看到酒吧的大小是 6而不是5 . 同样,b_f(5巴)的大小为 30而不是25 .

    所以,如果你是一个硬代码而不是 sizeof(...) ,这里会有问题。

    希望这有帮助。

        5
  •  2
  •   Afcrowe    15 年前

    所有这些都归结为记忆对齐。典型的32位机器每次尝试读取或写入4个字节的内存。这个结构是安全的,不会出现问题,因为它很容易落在4个字节以下,没有混淆的填充问题。

    如果结构是这样的话:

    class foo {
       unsigned char a;
       unsigned char b;
       unsigned char c;
       unsigned int i;
       unsigned int j;
    }
    

    你的同事逻辑可能会导致

    memcpy(pBuff,listOfFoos,11*SOME_NUM);
    

    (3个字符=3字节,2个整数=2*4字节,所以3+8)

    不幸的是,由于填充,结构实际上占用了12个字节。这是因为你不能将三个字符和一个int放入这个4字节的单词中,所以这里有一个字节的填充空间,它将int推入它自己的单词中。随着数据类型变得越来越多样化,这就越来越成为一个问题。

        6
  •  2
  •   Peter Mortensen Mohit    13 年前

    对于使用类似这样的东西,并且我无法避免的情况,我尝试在假设不再成立时使编译中断。我使用类似以下的东西(或 Boost.StaticAssert 如果情况允许的话):

    static_assert(sizeof(foo) <= 3);
    
    // Macro for "static-assert" (only usefull on compile-time constant expressions)
    #define static_assert(exp)           static_assert_II(exp, __LINE__)
    // Macro used by static_assert macro (don't use directly)
    #define static_assert_II(exp, line)  static_assert_III(exp, line)
    // Macro used by static_assert macro (don't use directly)
    #define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}
    
        7
  •  2
  •   Peter Mortensen Mohit    13 年前

    我本来是安全的,用一个 sizeof(foo) 我想。

    我的猜测是,为未来处理器架构优化的代码可能会引入某种形式的填充。

    试图找到这种虫子真的很痛苦!

        8
  •  1
  •   Mike Lewis    15 年前

    正如其他人所说,使用sizeof(foo)是更安全的选择。一些编译器(特别是嵌入式世界中的深奥编译器)会向类添加一个4字节的头。其他人可以根据您的编译器设置执行一些奇怪的内存对齐技巧。

    对于主流平台来说,你可能没问题,但这并不能保证。

        9
  •  0
  •   Jose Navarro    10 年前

    在两台计算机之间传递数据时,sizeof()可能仍然有问题。在其中一种情况下,代码可以使用填充进行编译,而在另一种情况下则不使用填充,在这种情况下,sizeof()将给出不同的结果。如果数组数据从一台计算机传递到另一台计算机,它将被误解,因为在预期的位置找不到数组元素。 一种解决方案是确保尽可能使用pragma pack(1),但这对于数组来说可能不够。最好的方法是预见到问题并使用填充到每个数组元素8个字节的倍数。

    推荐文章