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

时间:2009-11-04 20:24:08

标签: c++ data-structures packing bit-packing

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

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。)这些对象的数组被打包,它们之间没有填充。

我在两个平台(RedHat 64b,Solaris 9)上尝试过使用GNU,并且它在两个平台上都有效。

上述假设是否有效?如果没有,在什么条件下(例如OS /编译器的更改)可能会失败?

9 个答案:

答案 0 :(得分:21)

这样做肯定会更安全:

sizeof(foo) * SOME_NUM

答案 1 :(得分:18)

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

鉴于你正在使用char,假设可能是正确的,但C ++标准肯定不能保证。一个不同的编译器,甚至只是传递给当前编译器的标志的更改可能导致在结构的元素之间插入填充或者在结构的最后一个元素之后插入填充,或者两者兼而有之。

答案 2 :(得分:5)

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

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

只要您将pBuff分配到相同的大小,这将始终有效。 这样你就不会对填充和对齐做任何假设。

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

答案 3 :(得分:5)

我认为这是有效的原因,因为结构中的所有字段都是对齐的字符。如果至少有一个字段未对齐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

您可以看到Bar和F_B已对齐2,因此其字段i将正确对齐。您还可以看到Bar的大小 6而不是5 。同样,B_F(Bar的5)的大小 30而不是25

因此,如果你是硬代码而不是sizeof(...),那么你会遇到问题。

希望这有帮助。

答案 4 :(得分:2)

我会安全的,用sizeof(foo)来估算神奇的数字3。

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

试图追查那种错误是一种真正的痛苦!

答案 5 :(得分:2)

这一切都取决于内存对齐。典型的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个字节。这是因为你不能在这个4字节字中放入三个字符和一个int,因此有一个填充空格的字节将int推入它自己的字。这变得越来越成为一个问题,数据类型变得越来越多样化。

答案 6 :(得分:2)

对于使用这样的东西的情况,我无法避免它,我试图在假设不再成立时进行编译。我使用类似下面的内容(如果情况允许,则使用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 :(得分:1)

正如其他人所说,使用sizeof(foo)是一个更安全的赌注。一些编译器(尤其是嵌入式世界中的深奥编译器)将为类添加一个4字节的头。其他人可以使用时髦的内存对齐技巧,具体取决于您的编译器设置。

对于主流平台,你可能还不错,但这不是保证。

答案 8 :(得分:0)

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