填充字节何时复制-结构分配,按值传递,其他?

Art*_*wri 4 c struct padding memory-alignment

在调试问题时,出现以下问题。(请忽略较小的代码错误;该代码仅供参考。)

定义了以下结构:

typedef struct box_t {
  uint32_t x;
  uint16_t y;
} box_t;
Run Code Online (Sandbox Code Playgroud)

这个结构的实例通过值在函数之间传递(显然是简化的):

void fun_a(box_t b)
{
    ... use b ...
}

void fun_b(box_t bb)
{
    // pass bb by value
    int err = funa(bb);
}

void fun_c(void)
{
    box_t real_b;
    box_t some_b[10];
    ...
    ... use real_b and some_b[]  ...
    ...
    funb(real_b);
    funb(some_b[3]);
    ...
    box_t copy_b = some_b[5];
    ...
}
Run Code Online (Sandbox Code Playgroud)

在某些情况下,像这样比较box_t的两个实例:

 memcmp(bm, bn, sizeof(box_t));
Run Code Online (Sandbox Code Playgroud)

在几个嵌套的调用中,使用类似以下的方法来转储box_t arg的字节:

char *p = (char*) &a_box_t_arg;
for (i=0; i < sizeof(box_t); i++) {
    printf(" %02X", *p & 0xFF);
    p++;
}
printf("\n");
Run Code Online (Sandbox Code Playgroud)

sizeof(box_t)是8; 有2个填充字节(在uint16_t之后发现)。转储表明该结构的字段相等,但填充字节不相等。这导致memcmp失败(不足为奇)。

有趣的部分是发现“损坏的”填充值来自何处。向后跟踪后,发现一些box_t实例被声明为局部变量,并被初始化为:

box_t b;
b.x = 1;
b.y = 2;
Run Code Online (Sandbox Code Playgroud)

上面的代码没有(似乎)初始化填充字节,填充字节似乎包含“垃圾”(无论分配给b的堆栈空间中的内容是什么)。在大多数情况下,初始化是使用进行的memset(b, 0, sizeof(box_t))

问题是通过(1)结构赋值或(2)通过值传递来初始化box_t实例是否总是等效于大小为sizeof(box_t)的memcpy。是否曾经只复制了“真实字段”的6个字节(而不填充字节)。

从调试看来,memcpy sizeof(box_t)等效项总是完成的。是否有任何东西(例如,在标准中)实际上指定了这一点?知道随着调试的进行可以对填充字节的处理有什么帮助。

谢谢!(在Ubuntu LTS 10.4 64位上使用GCC 4.4.3)

奖励积分:

void f(void)
{
    box_t ba;
    box_t bb;
    box_t bc;
Run Code Online (Sandbox Code Playgroud)

3个实例被分配为16个字节,而sizeof()显示为8。为什么要增加空间?

Chr*_*oph 5

未指定填充字节的值(C99 / C11 6.2.6.1§6):

当将值存储在结构或联合类型的对象中(包括在成员对象中)时,与任何填充字节对应的对象表示形式的字节将使用未指定的值。

另请参见脚注42/51(C99:TC3,C1x草案):

因此,例如,结构分配不需要复制任何填充位。

编译器可以随意复制或不复制填充。在x86 [1]上,我的猜测是将复制2个尾随填充字节,但不会复制4个字节(即使在32位硬件上也可能发生,因为结构可能需要8字节对齐,例如允许原子读取)。double值)。

[1]未执行实际测量。


扩大答案:

对于填充字节,该标准不做任何保证。但是,如果使用静态存储持续时间初始化对象,则很有可能导致填充为零。但是,如果您使用该对象通过分配来初始化另一个对象,那么所有的赌注都将再次关闭(并且我希望尾随填充字节-再次,没有进行任何测量-将是特别好的候选对象,可以从复制中省略)。

即使在分配给单个成员时也使用memset()memcpy(),因为这也会使填充无效。-是一种在合理的实现中保证填充字节值的方法。但是,原则上,编译器可以随时随地更改填充值(可能与在寄存器中缓存成员有关-再次疯狂地猜测),可以通过使用volatile存储来避免这种情况。

唯一合理的解决方法的便携式我可以想到的是通过将合适尺寸的虚设部件,同时用特定编译器装置,其不需要额外的填充被引入(验证明确指定存储器布局__attribute__ ((packed))-Wpadded对于GCC)。