考虑以下简单结构:
struct A
{
float data[16];
};
假定平台float
是32位IEEE754浮点数(如果很重要),使用C ++标准保证struct A
的预期内存布局?如果没有,它能保证什么和/或什么是强制执行保证的方式?
通过期望的内存布局,我的意思是该结构占用了内存中的16*4=64
个字节,每个连续的4
个字节由单个float
占据。 data
数组。换句话说,预期的内存布局意味着以下测试通过:
static_assert(sizeof(A) == 16 * sizeof(float));
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
static_assert(offsetof(A, data[1]) == 1 * sizeof(float));
...
static_assert(offsetof(A, data[15]) == 15 * sizeof(float));
(offsetof
是合法的,因为A
是标准布局,请参见下文)
以防万一,请在gcc 9 HEAD的wandbox上测试actually passes。我从未遇到过平台和编译器的结合,它们会提供证据证明该测试可能会失败,并且我很想了解它们的存在。
alignas
说明符来处理它)。write_bytes(&x, sizeof(A))
。data
数组以传递该类型的单个对象,但是对于其中的一系列对象(例如,用于上传矩阵类型的顶点属性),仍然需要特定的内存布局。 据我所知,这些是struct A
可以预期的:
A
的指针可以reinterpret_cast
指向其第一个数据成员(大概是data[0]
?)的指针,即第一个成员之前 之前没有填充。根据标准, 不是 (据我所知)剩下的两个保证是:
data
中的struct A
数组后没有填充 。答案 0 :(得分:12)
不能保证布局的一件事是字节顺序,即多字节对象中字节的顺序。 write_bytes(&x, sizeof(A))
不是跨不同字节序的系统的可移植序列化。
A
可以reinterpret_cast
指向它的第一个数据成员的指针(大概是data[0]
吗?)
更正:第一个数据成员是data
,您可以使用其重新解释演员。至关重要的是,数组不能与第一个元素进行指针互换,因此您不能重新解释它们之间的强制转换。但是,地址肯定是相同的,据我所知,在data[0]
之后重新解释为std::launder
应该没问题。
原始类型数组的元素之间没有填充
保证数组是连续的。根据将元素放置到数组所需的填充来指定对象的sizeof
。 sizeof(T[10])
的大小恰好为sizeof(T * 10)
。如果相邻元素的非填充位之间存在填充,则该填充位于元素本身的末尾。
原始类型通常不能保证没有填充。例如,x86扩展精度long double
是80位,填充为128位。
char
,signed char
和unsigned char
确保没有填充位。 C标准(在这种情况下,C ++为其指定规范)保证固定宽度intN_t
和uintN_t
别名没有填充位。在不可能的系统上,不提供这些固定宽度类型。
答案 1 :(得分:2)
如果标准布局类对象具有任何非静态数据成员,则其 地址与其第一个非静态数据成员的地址相同。 否则,其地址与其一垒的地址相同 类子对象(如果有)。 [注意:因此,为了实现适当的对齐,在标准布局结构对象中可能会存在未命名的填充,但在其开始处不会存在。 —尾注]
因此,标准保证了
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
一个数组类型的对象包含一个连续分配的非空对象 N个类型为T的子对象的集合。
因此,以下是正确的
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
static_assert(offsetof(A, data[1]) == 1 * sizeof(float));
...
static_assert(offsetof(A, data[15]) == 15 * sizeof(float));