【发布时间】:2019-09-03 04:04:38
【问题描述】:
考虑以下简单结构:
struct A
{
float data[16];
};
我的问题是:
假设float
是一个32 位IEEE754 浮点数的平台(如果这很重要的话),C++ 标准是否保证struct A
的预期内存布局?如果不是,它保证什么和/或执行保证的方法是什么?
预期内存布局是指结构占用内存中的16*4=64
字节,每个连续的4
字节被data
数组中的单个float
占用。换句话说,预期内存布局意味着以下测试通过:
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。我从来没有遇到过一个平台和编译器的组合可以提供这个测试可能会失败的证据,如果它们确实存在,我很想了解它们。
为什么还要关心:
- 类似 SSE 的优化需要一定的内存布局(和对齐,我在这个问题中忽略了这一点,因为它可以使用标准的
alignas
说明符来处理)。 - 这种结构的序列化可以简单地归结为一个漂亮且可移植的
write_bytes(&x, sizeof(A))
。 - 某些 API(例如 OpenGL,特别是 glUniformMatrix4fv)期望这种精确的内存布局。当然,可以只传递指向
data
数组的指针来传递这种类型的单个对象,但是对于这些序列(例如,用于上传矩阵类型顶点属性)仍然需要特定的内存布局。李>
实际保证的内容:
据我所知,这些是 struct A
可以期待的:
- standard layout
- 由于是标准布局,指向
A
的指针可以是reinterpret_cast
指向其第一个数据成员的指针(大概是data[0]
?),即没有填充在第一个成员之前。
标准提供的其余两个(据我所知)保证是:
- 原始类型数组的元素之间没有填充 (我确信这是错误的,但我没有找到确认参考),
-
struct A
内部的data
数组 没有填充。
【问题讨论】:
-
C++ 2017 (draft n4659) 11.3.4,“数组” [dcl.array]:“数组类型的对象包含连续分配的非空
N
类型为T
的子对象集。” 1998 版除了在 8.3.4 中带有连字符的“子对象”之外具有相同的文本。 -
@EricPostpischil 感谢您的澄清!在这种情况下,“连续分配”到底是什么意思?
-
@lisyarus:它是“简单的英语”,或者至少是该领域从业者使用的英语——标准中没有正式定义。我很确定这意味着数组中元素的字节一个接一个地布局在内存中,元素之间没有填充。
-
在 C 中,不能保证剩下的第二个保证,并且有一些原因,“困难”的 C 实现可能会填充包含单个数组的结构。例如,我们可以想象一个实现会将
struct { char x[2]; }
填充到四个字节,如果它的目标硬件对内存的四字节字寻址有强烈的偏见,并且实现决定使所有结构至少四字节对齐以满足 C 标准对所有结构指针的一种表示的要求。我希望 C++ 类似,但不能自信地谈论它…… -
… 请注意,这是一种“理论上的”可能性。最有可能的是,
struct { float data[16]; }
不会被任何普通的 C 或 C++ 实现提供任何尾随填充——在任何普通的目标平台中都没有理由这样做。但是,在 C++ 标准中没有明确规范的情况下,唯一的保证方法是项目要求用于编译它的任何 C++ 实现都满足此属性。可以用断言对其进行测试。
标签: c++ language-lawyer memory-layout standard-layout