具有单个原始类型数组成员的标准布局结构的保证内存布局
Guaranteed memory layout for standard layout struct with a single array member of primitive type
考虑以下简单结构:
struct A
{
float data[16];
};
我的问题是:
假设一个平台,其中 float
是一个 32 位 IEEE754 浮点数(如果这很重要的话),C++ 标准是否保证 [=13= 的预期内存布局]?如果不是,它保证什么 and/or 执行保证的方法是什么?
通过预期的内存布局我的意思是该结构在内存中占用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。我从未见过平台和编译器的组合可以提供该测试可能失败的证据,如果它们确实存在,我很乐意了解它们。
为什么还要关心:
- 类似 SSE 的优化需要一定的内存布局(和对齐,我在这个问题中忽略了这一点,因为它可以使用标准
alignas
说明符处理)。
- 这种结构的序列化将简单地归结为一个漂亮且可移植的
write_bytes(&x, sizeof(A))
。
- 一些 API(例如 OpenGL,具体地说,glUniformMatrix4fv)需要这种确切的内存布局。当然,可以只传递指向
data
数组的指针来传递这种类型的单个对象,但是对于这些对象的序列(例如,用于上传矩阵类型的顶点属性),仍然需要特定的内存布局。
实际保证的是:
据我所知,这些是 struct A
可以预期的事情:
- 是standard layout
- 作为标准布局的结果,指向
A
的指针可以 reinterpret_cast
指向指向其第一个数据成员的指针(大概是 data[0]
?) ,即在第一个成员之前没有填充。
标准提供的不是(据我所知)的两个剩余保证是:
- 原始类型数组的元素之间没有填充(我确信这是错误的,但我失败了找到一个确认参考),
- 在
struct A
. 中的 data
数组 之后 没有填充
关于布局不能保证的一件事是字节顺序,即多字节对象中的字节顺序。 write_bytes(&x, sizeof(A))
不是跨具有不同字节顺序的系统的可移植序列化。
A
can be reinterpret_cast
to a pointer to its first data member (which is, presumably, data[0]
?)
更正:第一个数据成员是 data
,您可以用它重新解释 cast。至关重要的是,数组不能与其第一个元素进行指针互换,因此您不能重新解释它们之间的强制转换。但是,地址保证是相同的,因此据我所知,在 std::launder
之后重新解释为 data[0]
应该没问题。
There is no padding in between elements of an array of primitive type
保证数组是连续的。对象的 sizeof
是根据将元素放入数组所需的填充来指定的。 sizeof(T[10])
的大小恰好是 sizeof(T) * 10
。如果相邻元素的非填充位之间有填充,则该填充位于元素本身的末尾。
原始类型通常不保证没有填充。例如x86扩展精度long double
为80位,填充为128位。
char
、signed char
和 unsigned char
保证没有填充位。 C 标准(在这种情况下 C++ 委托规范)保证固定宽度 intN_t
和 uintN_t
别名没有填充位。在不可能的系统上,不提供这些固定宽度的类型。
If a standard-layout class object has any non-static data members, its
address is the same as the address of its first non-static data member.
Otherwise, its address is the same as the address of its first base
class subobject (if any). [Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. — end note]
因此,标准保证
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
An object of array type contains a contiguously allocated non-empty
set of N subobjects of type 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));
考虑以下简单结构:
struct A
{
float data[16];
};
我的问题是:
假设一个平台,其中 float
是一个 32 位 IEEE754 浮点数(如果这很重要的话),C++ 标准是否保证 [=13= 的预期内存布局]?如果不是,它保证什么 and/or 执行保证的方法是什么?
通过预期的内存布局我的意思是该结构在内存中占用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。我从未见过平台和编译器的组合可以提供该测试可能失败的证据,如果它们确实存在,我很乐意了解它们。
为什么还要关心:
- 类似 SSE 的优化需要一定的内存布局(和对齐,我在这个问题中忽略了这一点,因为它可以使用标准
alignas
说明符处理)。 - 这种结构的序列化将简单地归结为一个漂亮且可移植的
write_bytes(&x, sizeof(A))
。 - 一些 API(例如 OpenGL,具体地说,glUniformMatrix4fv)需要这种确切的内存布局。当然,可以只传递指向
data
数组的指针来传递这种类型的单个对象,但是对于这些对象的序列(例如,用于上传矩阵类型的顶点属性),仍然需要特定的内存布局。
实际保证的是:
据我所知,这些是 struct A
可以预期的事情:
- 是standard layout
- 作为标准布局的结果,指向
A
的指针可以reinterpret_cast
指向指向其第一个数据成员的指针(大概是data[0]
?) ,即在第一个成员之前没有填充。
标准提供的不是(据我所知)的两个剩余保证是:
- 原始类型数组的元素之间没有填充(我确信这是错误的,但我失败了找到一个确认参考),
- 在
struct A
. 中的
data
数组 之后 没有填充
关于布局不能保证的一件事是字节顺序,即多字节对象中的字节顺序。 write_bytes(&x, sizeof(A))
不是跨具有不同字节顺序的系统的可移植序列化。
A
can bereinterpret_cast
to a pointer to its first data member (which is, presumably,data[0]
?)
更正:第一个数据成员是 data
,您可以用它重新解释 cast。至关重要的是,数组不能与其第一个元素进行指针互换,因此您不能重新解释它们之间的强制转换。但是,地址保证是相同的,因此据我所知,在 std::launder
之后重新解释为 data[0]
应该没问题。
There is no padding in between elements of an array of primitive type
保证数组是连续的。对象的 sizeof
是根据将元素放入数组所需的填充来指定的。 sizeof(T[10])
的大小恰好是 sizeof(T) * 10
。如果相邻元素的非填充位之间有填充,则该填充位于元素本身的末尾。
原始类型通常不保证没有填充。例如x86扩展精度long double
为80位,填充为128位。
char
、signed char
和 unsigned char
保证没有填充位。 C 标准(在这种情况下 C++ 委托规范)保证固定宽度 intN_t
和 uintN_t
别名没有填充位。在不可能的系统上,不提供这些固定宽度的类型。
If a standard-layout class object has any non-static data members, its address is the same as the address of its first non-static data member. Otherwise, its address is the same as the address of its first base class subobject (if any). [Note: There might therefore be unnamed padding within a standard-layout struct object, but not at its beginning, as necessary to achieve appropriate alignment. — end note]
因此,标准保证
static_assert(offsetof(A, data[0]) == 0 * sizeof(float));
An object of array type contains a contiguously allocated non-empty set of N subobjects of type 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));