Cast一个原始类型指针指向一个结构指针 - 对齐和填充?

时间:2012-08-27 08:02:55

标签: c++ casting

当我回答问题时,只有20分钟的年龄,我想出了一个有趣的场景,我不确定这种行为:

让我有一个大小为n的整数数组,由intPtr;

指向
int* intPtr;

让我也有这样的结构:

typedef struct {
int val1;
int val2;
//and less or more integer declarations goes on like this(not any other type)
}intStruct;

我的问题是我是否进行演员intStruct* structPtr = (intStruct*) intPtr;

如果我遍历结构的元素,我确定能正确获取每个元素吗?在任何架构/编译器中是否存在未对齐(可能因为填充)的可能性?

6 个答案:

答案 0 :(得分:5)

标准是相当具体的,即使是POD结构(我相信最严格的结构类)也可以在成员之间进行填充。 (“因此,在POD结构对象中可能存在未命名的填充,但在开始时不是必要的,以实现适当的对齐。” - 非规范性注释,但仍然使意图非常清楚。)

例如,对比标准布局结构(C ++ 11,§1.8/ 4)的要求:

  

平凡可复制或标准布局类型(3.9)的对象应占用连续的存储字节。“

...与数组(§8.3.4/ 1):

  

数组类型的对象包含一个连续分配的非空N个子类型的T类型。

在数组中,元素本身需要连续分配,而在结构中,只需要存储是连续的。

可能使“连续存储”要求更有意义的第三种可能性是考虑一个不易于复制或标准布局的结构/类。在这种情况下,存储可能根本不是连续的。例如,实现可能会留出一个内存区域来保存所有私有变量,以及一个完全独立的内存区域来保存所有公共变量。为了使其更具体,请考虑两个定义:

class A { 
    int a;
public:
    int b;
} a;

class B {
    int x;
public:
    int y;
} b;

根据这些定义,内存可能会布置如下:

a.a;
b.x;

// ... somewhere else in memory entirely:

a.b;
b.y;

在这种情况下,存储元素都不需要是连续的,因此允许交错部分完全独立的结构/类。

也就是说,第一个元素必须与结构整体位于同一地址(9.2 / 17):“指向POD结构对象的指针,使用reinterpret_cast进行适当转换,指向其初始成员(或如果该成员是一个位字段,那么它到它所在的单位),反之亦然。“

在你的情况下,你有一个POD结构,所以(§9.2/ 17):“指向POD结构对象的指针,使用reinterpret_cast进行适当转换,指向其初始成员(或者如果该成员是比特字段,然后到它所在的单位,反之亦然。“由于第一个成员必须对齐,而其余成员都是相同的类型,因此在其他成员之间不可能真正需要任何填充(即,除了位字段,任何类型你可以放入一个结构,你也可以放入一个数组,其中需要连续分配元素)。如果你有一个小于单词的元素,在面向单词的机器上(例如,早期的DEC Alphas),填充可能会使访问更简单一些。例如,早期的DEC Alphas(在硬件级别)只能一次读/写一个完整的(64位)字。因此,让我们考虑类似四个char元素的结构:

struct foo { 
   char a, b, c, d;
};

如果需要将它们放在内存中以便它们是连续的,那么访问foo::b(例如)将要求CPU加载该字,然后将其向右移8位,然后屏蔽为零 - 扩展该字节以填充整个寄存器。

存储会更糟糕 - CPU必须加载整个单词的当前值,屏蔽掉相应的char大小的当前内容,将新值移动到正确的位置,或者它进入单词,最后存储结果。

相比之下,在元素之间使用填充,每个元素都变成一个简单的加载/存储,没有移位,屏蔽等。

至少如果内存服务,使用DEC的Alpha正常编译器,int为32位,long为64位(它先于long long)。因此,使用四个int s的结构,您可能希望在元素之间看到另外32位填充(以及最后一个元素之后的另外32位)。

鉴于你确实有一个POD结构,你仍然有一些可能性。我可能更喜欢的是使用offsetof获取结构成员的偏移量,创建它们的数组,并通过这些偏移量访问成员。我在coupleprevious answers中展示了如何执行此操作。

答案 1 :(得分:3)

严格地说,不允许这样的指针强制转换并导致未定义的行为。

然而,演员的主要问题是编译器可以在结构内的任何地方自由添加任意数量的填充字节,除非在第一个元素之前。因此它是否起作用取决于特定系统的对齐要求,以及是否启用了struct padding。

int不一定与可寻址数据块的最佳大小相同,即使对于大多数32位系统也是如此。有些32位不关心错位,有些会导致错位,但产生的代码效率会降低,有些必须使数据对齐。理论上,64位可能还希望在int(在那里是32位)之后添加填充以获得64位块,但实际上它们支持32位指令集。

如果您编写依赖此演员表的代码,则应添加以下内容:

static_assert (sizeof(intStruct) == 
               sizeof(int) + sizeof(int));

答案 2 :(得分:3)

鉴于元素类型是标准布局,它保证是合法的。注意:以下所有引用均为标准。

  

8.3.4数组[dcl.array]

     

1 - [...]数组类型的对象包含一组N类型T子对象的连续分配的非空对象。 [...]

关于struct N成员类型T

  

9.2班级成员[class.mem]

     

14 - 分配具有相同访问控制的(非联合)类的非静态数据成员   后来的成员在类对象中有更高的地址。 [......]实施对齐要求可能   导致两个相邻成员不能在彼此之后立即分配[...]
  20 - 指向标准布局结构对象的指针,适当地使用reinterpret_cast转换,指向它   初始成员[...],反之亦然。 [ 注意:   因此,在标准布局结构对象中可能存在未命名的填充,但不是在其开头,   必要时,以实现适当的对齐。 - 后注]

所以问题是struct内是否需要任何对齐填充可能导致其成员不能相互连续分配。答案是:

  

1.8 C ++对象模型[intro.object]

     

4 - [...]一个易于复制或标准布局类型的对象应占用连续的存储字节。

换句话说,标准布局struct a包含至少两个不尊重相同(标准布局)类型的成员xy身份&a.y == &a.x + 1违反了1.8:4。

请注意, alignment 定义为( 3.11 Alignment [basic.align] 连续地址之间可以分配给定对象的字节数;因此,类型T的对齐不能大于T数组中相邻对象之间的距离,并且(因为 5.3.3 Sizeof [expr.sizeof] 指定 n 元素数组的大小 n 乘以元素的大小alignof(T)可能不会更大比sizeof(T)。因此,相同类型的结构的相邻元素之间的任何附加填充将不会通过对齐所需,因此不会被9.2:14支持。


关于AProgrammer的观点,我会解释 26.4复数[complex.numbers] 中的语言,要求std::complex<T>的实例化应该表现为标准布局类型到其成员的位置,而不需要符合标准布局类型的所有要求。

答案 3 :(得分:2)

这种行为几乎肯定是编译器,架构和ABI依赖的。但是,如果您正在使用gcc,则可以使用__attribute__((packed))强制编译器一个接一个地打包结构成员,而不进行任何填充。这样,内存布局应该与平面阵列匹配。

答案 4 :(得分:1)

我找不到任何保证它在我前一段时间搜索时有效的内容,而且我已经找到了std :: complex&lt;&gt;的明确保证。在C ++中,如果它更普遍是真的可以更容易地制定,所以我怀疑我在搜索中遗漏了一些东西(但缺乏证据几乎不是缺席的证据,而且标准有时在其制定中模糊不清)。

答案 5 :(得分:1)

C结构的典型对齐保证了结构中的数据结构成员将按顺序存储,这与C数组相同。所以订单不是问题。

在对齐时,由于您只有一种数据类型(int),尽管编译器有资格这样做,但是没有必要添加填充以对齐数据成员。编译器可以在结构开头之前添加填充,但是它不能在数据结构的开头添加填充。因此,如果编译器要在您的情况下添加填充,

而不是: [4Byte int] [4Byte int] [4Byte int] ... [4Byte int]

您的数据结构必须像这样存储:
[4Byte Data] [4Byte Padding] [4Byte Data] ......这是不合理的。

总的来说,我认为这种演员应该在你的情况下没有问题,但我认为使用它是不好的做法。