C ++数据成员对齐和阵列包装
-
16-09-2019 - |
题
在一个代码评审我已经遇到一些代码,定义了一个简单的结构,如下所示:
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
}
其他方面,这些对象的数组被定义:
foo listOfFoos[SOME_NUM];
后来,结构被原始复制到一个缓冲器:
memcpy(pBuff,listOfFoos,3*SOME_NUM);
此代码依赖于以下假设:a)本FOO的大小是3,并且没有填充被施加,以及b)这些对象的阵列填充在它们之间没有填充
。我已经与GNU试图在两个平台(红帽64b中的Solaris 9),和它的工作在两个
如上有效的假设?如果不是,在什么条件下(例如,在OS /编译器的变化)可能他们会失败?
解决方案
对象数组需要是连续的,所以有对象之间从未填充,虽然填充可以被添加到的物体的端部(产生几乎相同的效果)。
既然你与焦炭的工作,假设可能是正确的往往不是,但C ++标准肯定不能保证。不同的编译器,或甚至只是在传递到您的当前的编译器的标志的变化可能会导致填充被插入该结构,或两者的最后一个元素之后的该结构的元件或之间。
其他提示
这肯定是比较安全的事:
sizeof(foo) * SOME_NUM
如果您复制数组这样你应该使用
memcpy(pBuff,listOfFoos,sizeof(listOfFoos));
这将永远只要你PBUFF分配到相同大小的工作。 这样,你随时都在做填充和对齐没有假设。
大多数编译器对齐结构或类以包括最大类型的所需的对准。在字符的情况下,这意味着没有对准和填充,但是如果添加短例如你的类将是6个字节的大与最后一个字符和你的短之间追加填充的一个字节。
我觉得这工作,因为所有的结构字段是焦炭,整的原因之一。如果存在未对齐1的至少一个字段,所述结构的对准/类将不为1。
(将取决于场顺序和取向的取向)让看到一些示例:
#include <stdio.h>
#include <stddef.h>
typedef struct {
unsigned char a;
unsigned char b;
unsigned char c;
} Foo;
typedef struct {
unsigned short i;
unsigned char a;
unsigned char b;
unsigned char c;
} Bar;
typedef struct { Foo F[5]; } F_B;
typedef struct { Bar B[5]; } B_F;
#define ALIGNMENT_OF(t) offsetof( struct { char x; t test; }, test )
int main(void) {
printf("Foo:: Size: %d; Alignment: %d\n", sizeof(Foo), ALIGNMENT_OF(Foo));
printf("Bar:: Size: %d; Alignment: %d\n", sizeof(Bar), ALIGNMENT_OF(Bar));
printf("F_B:: Size: %d; Alignment: %d\n", sizeof(F_B), ALIGNMENT_OF(F_B));
printf("B_F:: Size: %d; Alignment: %d\n", sizeof(B_F), ALIGNMENT_OF(B_F));
}
当执行时,其结果是:
Foo:: Size: 3; Alignment: 1
Bar:: Size: 6; Alignment: 2
F_B:: Size: 15; Alignment: 1
B_F:: Size: 30; Alignment: 2
您可以看到,酒吧和F_B具有对准2,使该领域,我会正确对齐。还可以看到,酒吧的大小为 6,而不是5 的。类似地,B_F的大小(栏5)为 30,而不是25
所以,如果你是一个硬编码,而不是sizeof(...)
,在这里你会得到一个问题。
希望这有助于。
这一切都归结到内存对齐。典型的32位机器读取或写入4个字节的每尝试存储器。这种结构是安全的,从问题,因为它没有混乱的填充问题落在根据该4个字节容易。
现在,如果结构是这样:
class foo {
unsigned char a;
unsigned char b;
unsigned char c;
unsigned int i;
unsigned int j;
}
您同事逻辑可能会导致
memcpy(pBuff,listOfFoos,11*SOME_NUM);
(3字符的= 3个字节,2个整数= 2 * 4个字节,所以3 + 8)
不幸的是,由于填补所述结构实际上占用12个字节。这是因为你不能放到三个字符的和一个int成4字节字,所以有补齐空间有一个字节其推动INT到它自己的话。这将成为一个问题的更多样化的数据类型成为越来越多。
对于使用这样的东西的情况下,我不能回避它,我尽量让编译休息时,推定不再持有。我使用类似下面的(或 Boost.StaticAssert 一>如果情况允许的话):
static_assert(sizeof(foo) <= 3);
// Macro for "static-assert" (only usefull on compile-time constant expressions)
#define static_assert(exp) static_assert_II(exp, __LINE__)
// Macro used by static_assert macro (don't use directly)
#define static_assert_II(exp, line) static_assert_III(exp, line)
// Macro used by static_assert macro (don't use directly)
#define static_assert_III(exp, line) enum static_assertion##line{static_assert_line_##line = 1/(exp)}
我会一直安全,并用sizeof(foo)
我想更换一个神奇的数字3。
我的猜测是为未来的处理器体系结构优化的代码可能会引入一些形式填充。
和试图追查该类型的错误是一个真正的痛苦!
正如其他人表示,采用的sizeof(富)是一个更安全的选择。一些编译器(尤其是在嵌入式领域深奥的)将一个4字节的头添加到类。别人能做到的时髦内存对齐的招数,这取决于你的编译器设置。
对于主流平台,你可能没事,但它不是一个保证。
有可能仍然具有的sizeof一个问题,当要传递两台计算机之间的数据()。在其中一人的代码可能与填充和在其他没有编译,在这种情况下的sizeof()会得到不同的结果。如果数组数据从一个计算机传送到另一个会被误解,因为该数组的元素将不被找到预期。 一种解决方案是确保的#pragma包(1)尽可能使用,但可能没有足够的阵列。最好是预见的问题,并使用填充到的每个数组元素对应8个字节的倍数。