C 何时复制焊盘字节-结构赋值、按值传递、其他?

C 何时复制焊盘字节-结构赋值、按值传递、其他?,c,struct,padding,memory-alignment,C,Struct,Padding,Memory Alignment,调试问题时,出现了以下问题。(请忽略较小的代码错误;代码仅用于说明。) 定义了以下结构: typedef struct box_t { uint32_t x; uint16_t y; } box_t; 此结构的实例在函数之间按值传递(明显简化): 在某些情况下,对box_t的两个实例进行如下比较: memcmp(bm, bn, sizeof(box_t)); char *p = (char*) &a_box_t_arg; for (i=0; i < sizeof(bo

调试问题时,出现了以下问题。(请忽略较小的代码错误;代码仅用于说明。)

定义了以下结构:

typedef struct box_t {
  uint32_t x;
  uint16_t y;
} box_t;
此结构的实例在函数之间按值传递(明显简化):

在某些情况下,对box_t的两个实例进行如下比较:

 memcmp(bm, bn, sizeof(box_t));
char *p = (char*) &a_box_t_arg;
for (i=0; i < sizeof(box_t); i++) {
    printf(" %02X", *p & 0xFF);
    p++;
}
printf("\n");
box_t b;
b.x = 1;
b.y = 2;
box_t real_b = { 0 };
int box_isequal (box_t bm, box_t bn)
{
    return (bm.x == bn.x) && (bm.y == bn.y);
}
在几个嵌套调用中,box_t arg的字节使用如下方式转储:

 memcmp(bm, bn, sizeof(box_t));
char *p = (char*) &a_box_t_arg;
for (i=0; i < sizeof(box_t); i++) {
    printf(" %02X", *p & 0xFF);
    p++;
}
printf("\n");
box_t b;
b.x = 1;
b.y = 2;
box_t real_b = { 0 };
int box_isequal (box_t bm, box_t bn)
{
    return (bm.x == bn.x) && (bm.y == bn.y);
}
上面没有(似乎)初始化pad字节,它似乎包含“垃圾”(为b分配的堆栈空间中的任何内容)。在大多数情况下,初始化是使用
memset(b,0,sizeof(box_t))
完成的

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

从调试中可以看出,memcpy sizeof(box_t)等效项总是完成的。是否有任何东西(例如,在标准中)实际规定了这一点?随着调试的进行,了解pad字节的处理可以依靠什么是很有帮助的

谢谢!(在Ubuntu LTS 10.4 64位上使用GCC4.4.3)

奖励积分:

void f(void)
{
    box_t ba;
    box_t bb;
    box_t bc;

这3个实例的分配间隔为16字节,而sizeof()显示为8字节。为什么需要额外的空间?

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

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

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

因此,例如,结构赋值不需要复制任何填充位

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

[1] 没有进行实际测量


要详细说明答案,请执行以下操作:

该标准对填充字节没有任何保证。但是,如果使用静态存储持续时间初始化对象,则很有可能以零填充结束。但是,如果您使用该对象通过赋值来初始化另一个对象,那么所有的赌注都将再次失效(我希望尾部填充字节——同样,没有进行测量——是特别好的候选者,可以从复制中省略)

使用
memset()。但是,原则上,编译器可以随时“在背后”更改填充值(这可能与在寄存器中缓存成员有关-再次疯狂猜测),使用
volatile
存储可能可以避免这种情况


我能想到的唯一合理可移植的解决方法是通过引入适当大小的虚拟成员来明确指定内存布局,同时使用特定于编译器的方法验证没有引入额外的填充(
\uuuuuu attribute\uuuuuuu((打包))
-wpsadded
,对于gcc).C11将允许您定义匿名结构和联合成员:

typedef union box_t {
  unsigned char allBytes[theSizeOfIt];
  struct {
    uint32_t x;
    uint16_t y;
  };
} box_t;
该联合的行为与以前几乎相同,您可以访问
.x
等,但默认的初始化和分配将更改。如果始终确保变量按如下方式正确初始化:

 memcmp(bm, bn, sizeof(box_t));
char *p = (char*) &a_box_t_arg;
for (i=0; i < sizeof(box_t); i++) {
    printf(" %02X", *p & 0xFF);
    p++;
}
printf("\n");
box_t b;
b.x = 1;
b.y = 2;
box_t real_b = { 0 };
int box_isequal (box_t bm, box_t bn)
{
    return (bm.x == bn.x) && (bm.y == bn.y);
}
还是像这样

box_t real_a = { .allBytes = {0}, .x = 1, .y = 2 };
所有填充字节应正确初始化为
0
。如果您的整数类型有填充位,这将没有帮助,但至少您选择的
uintXX\t
类型在定义上没有填充位

gcc和追随者已经实现了这个扩展,即使他们还没有完全实现C11

编辑:中有一个宏以一致的方式执行此操作:

#define P99_DEFINE_UNION(NAME, ...)                     \
 union NAME {                                           \
   uint8_t p00_allbytes[sizeof(union { __VA_ARGS__ })]; \
   __VA_ARGS__                                          \
 }

也就是说,数组的大小是通过声明一个“untaged”并集来确定的。正如Christoph所说,没有关于填充的保证。最好不要使用
memcmp
来比较两个结构。它在错误的抽象级别上工作
memcmp
在表示上按字节工作,同时需要比较成员的值

最好使用一个单独的比较函数,它接受两个结构并分别比较每个成员。大概是这样的:

 memcmp(bm, bn, sizeof(box_t));
char *p = (char*) &a_box_t_arg;
for (i=0; i < sizeof(box_t); i++) {
    printf(" %02X", *p & 0xFF);
    p++;
}
printf("\n");
box_t b;
b.x = 1;
b.y = 2;
box_t real_b = { 0 };
int box_isequal (box_t bm, box_t bn)
{
    return (bm.x == bn.x) && (bm.y == bn.y);
}

作为奖励,这三个对象是独立的对象,它们不是同一数组的一部分,它们之间不允许使用指针算法。作为函数局部变量,它们通常在堆栈上分配,并且由于它们是独立的,编译器可以以任何最佳方式将它们对齐,例如,为了性能。

这包括使用{0}进行初始化吗?即box_t b={0};不保证将任何pad字节设置为零?(IOW标准语句中的“存储”是否包括或排除初始化?)。编译器可以随心所欲地处理它们。@DanielFischer:如果结构中的最后一个字段是名为
foo
char
,后跟三个字节的填充,其中一个执行
mystruct.foo++
,那么对于32位小尾端系统,编译器是否可以自由地加载
foo
作为32位整数,将其递增,并将其存储为32位int,前提是每当
foo
升级为更大的类型时,它都会过滤掉高位?@supercat,除非我完全误读了标准,是的,绝对如此。是否有一种平台无关的方法来赋予SizeOfit其值?就这么定了