结构对齐问题

时间:2011-08-23 07:43:49

标签: c++ c

typedef struct {    
    char c;
    char cc[2];
        short s;
    char ccc;
}stuck;

上面的结构应该有这样的内存布局吗?

1   2   3   4   5  6     7
- c -  cc   -   s  - ccc -

还是这个?

1    2   3   4    5   6   7     8 
-    c   -   cc   -   s   - ccc -

我认为第一个应该更好,但为什么我的VS09编译器会选择第二个呢? (顺便说一句,我的布局是否正确?)谢谢

5 个答案:

答案 0 :(得分:4)

我认为您的结构将具有以下布局,至少在Windows上:

typedef struct {    
    char c;
    char cc[2];
    char __padding;
    short s;
    char ccc;
    char __tail_padding;
} stuck;

您可以通过重新排序结构成员来避免填充:

typedef struct {    
    char c;
    char cc[2];
    char ccc;
    short s;
} stuck;

答案 1 :(得分:2)

编译器无法选择第二个。标准要求第一个字段必须与结构的开头对齐。

您是否使用offsetof中的stddef.h来查找此内容?

6.7.2.1 - 13

  

指向适当转换的结构对象的指针指向它   初始成员(或者如果该成员是一个位域,那么到该单位)   它居住的地方),反之亦然。 可能有未命名的填充   在结构对象中,但不在其开头

这意味着你可以拥有

struct s {
    int x;
    char y;
    double z;
};

struct s obj;
int *x = (int *)&obj; /* Legal. */

换句话说

offsetof(s, x); /* Must yield 0. */

答案 2 :(得分:2)

除了在结构的开头,实现可以在结构中放置它想要的填充,因此没有正确的方式。从C99 6.7.2.1结构和联合说明符,段落:

  

/ 12:
结构或联合对象的每个非位字段成员都以适合其类型的实现定义方式对齐。

     

/ 13:
可能有未命名的   在结构对象中填充,但不在其开头。

     

/ 15:
在结构或联合的末尾可能有未命名的填充。

第13段还包含:

  

在结构对象中,非位字段成员和位字所在的单位的地址会按声明的顺序增加。

这意味着结构中的字段无法重新排序。并且,在大量现代实现中(但这是不是标准规定的),对象的对齐等于其大小。例如,32位整数数据类型可能具有四(8位)字节的对齐要求。

因此,逻辑对齐将是:

offset  size  field
------  ----  -----
   0      1   char c;
   1      2   char cc[2];
   3      1   padding
   4      2   short s;
   6      1   char ccc;
   7      1   padding

但是,如上所述,它可能是不同的东西。最后的填充是为了确保连续的数组元素正确对齐(因为short最有可能必须在2字节边界上)。

您可以通过多种(非便携式)方式控制填充。许多编译器都有#pragma pack选项可用于控制填充(尽管要小心:虽然某些系统在访问未对齐数据时可能会放慢速度,但有些系统实际上会将核心转储为非法访问)。

此外,将结构中的元素从最大到最小重新排序往往会减少填充,因为较大的元素往往具有更严格的对齐要求。

这些以及更为丑陋的“解决方案”将更多地讨论here

答案 3 :(得分:1)

虽然我确实理解你对齐的视觉表现,但我可以告诉你,使用VS你可以通过使用'pragma'实现打包结构:

__pragma( pack(push, 1) )
struct { ... };
__pragma( pack(pop) )

一般来说,struct-alignment取决于所使用的编译器,目标平台(及其地址大小)和天气,IOW实际上它没有很好地定义。

答案 4 :(得分:0)

其他人提到可以在属性之间或最后一个属性之后引入填充。

但我相信,有趣的是要了解为什么

类型通常具有对齐。此属性可以精确确定哪个地址对特定类型有效(或不有效)。在某些架构上,这是一个宽松的要求(如果你不尊重它,你只会产生一些开销),对其他架构,违反它会导致硬件异常。

例如(任意,因为每个平台都定义了自己的):

  • char:1
  • short(16位):2
  • int(32位):4
  • long int(64位):8

复合类型通常具有对齐其各部分对齐的最大值。


对齐如何影响填充?

为了尊重类型的对齐,可能需要一些填充,例如:

struct S { char a; int b; };

align(S) = max(align(a), align(b)) = max(1, 4) = 4

因此我们有:

// S allocated at address 0x16 (divisible by 4)
0x16 a
0x17 
0x18
0x19
0x20 b
0x21 b
0x22 b
0x23 b

请注意,因为b只能在一个也可被4整除的地址上分配,所以ab之间会有一些空格,这个空间称为填充。


填充来自何处?

填充可能有两个不同的原因:

  • 属性之间,它是由对齐差异引起的(见上文)
  • struct的末尾,它是由数组要求引起的

数组要求是应该分配数组的元素而不插入填充。这允许使用指针算法从元素导航到另一个元素:

+---+---+---+
| S | S | S |
+---+---+---+

S* p = /**/;
p = p + 1; // <=> p = (S*)((void*)p + sizeof(S));

但是,这意味着结构S的大小需要是S对齐的倍数。

示例:

struct S { int a; char b; };

+----+-+---+
|  a |b| ? |
+----+-+---+

a: offset 0, size 4
b: offset 4, size 1
?: offset 5, size 3 (padding)

完全放弃:

typedef struct {    
    char a;
    char b[2];
    short s;
    char c;
} stuck;

+-+--+-+--+-+-+
|a| b|?|s |c|?|
+-+--+-+--+-+-+

如果你真的希望避免填充,一个(简单的)技巧(不涉及加法或减法)就是从最大对齐开始简单地命令你的属性。

typedef struct {
  short s;
  char a;
  char b[2];
  char c;
} stuck;

+--+-+--+-+
| s|a| b|c|
+--+-+--+-+

这是一个简单的经验法则,特别是基本类型的对齐可能会从平台更改为平台(32位/ 64位),而类型的相对顺序非常稳定(例外:指针)。