当填充字节复制 - 结构分配,按值传递,其他的?(When are pad bytes copied

2019-06-25 09:01发布

在调试的问题,下面的问题上来。 (请忽略次要代码错误;代码仅仅是一个例子。)

下面的结构被定义:

typedef struct box_t {
  uint32_t x;
  uint16_t y;
} box_t;

这个结构的实例被按值传递从函数到功能(显然简化):

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];
    ...
}

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

 memcmp(bm, bn, sizeof(box_t));

在几个嵌套调用,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");

所述的sizeof(box_t)为8; 有2个填充字节(发现作为uint16_t后被)。 转储表明,该结构的领域是相等的,但填充字节不是; 这引起了memcmp失败(这并不奇怪)。

有趣的部分已被发现,其中“损坏”填充值是从哪里来的。 向后跟踪后,人们发现,一些box_t实例被宣布为局部变量并初始化是这样的:

box_t b;
b.x = 1;
b.y = 2;

上述办法并不(出现)初始化的填充字节,这看起来包含“垃圾”(无论是在分配给b中的堆栈空间)。 在大多数情况下,初始化用做memset(b, 0, sizeof(box_t))

的问题是,是否通过由值:(1)结构分配或(2)通过初始化box_t的一个实例将永远做的sizeof(box_t)的的memcpy的等价物。 难道永远只有6个字节“真正的字段的情况下被复制(和填充字节都没有)。

从调试它看来的memcpy的sizeof(box_t),相当于总是完成。 有什么(例如,在标准),实际上这个规定? 这将有助于知道什么可以作为调试前进关于填充字节的处理计算。

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

奖励积分:

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

的3个实例被分配开的16个字节而的sizeof()表示8.为什么额外的空间?

Answer 1:

的填充字节的值是不确定的(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)。



Answer 2:

C11将让你定义匿名结构和联合的成员:

typedef union box_t {
  unsigned char allBytes[theSizeOfIt];
  struct {
    uint32_t x;
    uint16_t y;
  };
} box_t;

该联盟将表现几乎和以前一样,你可以访问.x等,但默认初始化和赋值会改变。 如果您始终确保您的变量是正确的初始化是这样的:

box_t real_b = { 0 };

或像这样

box_t real_a = { .allBytes = {0}, .x = 1, .y = 2 };

所有的填充字节应正确初始化为0 。 这不会帮助,如果你的整数类型将有填充比特 ,但至少uintXX_t您选择的类型将不会被定义为拥有它们。

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

编辑:在P99有一个宏这样做以一致的方式:

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

这是阵列的尺寸是通过声明一个“未标记”联盟只是它的大小来确定。



Answer 3:

正如克里斯托弗说,有关于填充任何保证。 最好的办法是不使用memcmp比较两个结构。 它工作在错误的抽象水平。 memcmp作品逐个字节的表现,当你需要比较成员的值。

最好使用一个单独的比较功能采用两个结构和各部件分别进行比较。 事情是这样的:

int box_isequal (box_t bm, box_t bn)
{
    return (bm.x == bn.x) && (bm.y == bn.y);
}

为了您的奖金,这三个对象是单独的对象,他们是不一样的数组和指针运算的部分之间是不允许他们。 作为函数的局部变量,它们通常是在栈中分配,因为它们是独立的编译器可以以任何方式是最好的,例如用于性能对齐。



文章来源: When are pad bytes copied - struct assignment, pass by value, other?