带有原始类型的单个数组成员的标准布局结构的有保证的内存布局 [英] Guaranteed memory layout for standard layout struct with a single array member of primitive type
问题描述
考虑以下简单结构:
struct A
{
float data[16];
};
我的问题是:
假设平台上的 float
是32位IEEE754浮点数(如果很重要),使用C ++标准保证 struct A的预期内存布局
?如果没有,它能保证什么和/或什么是强制执行保证的方式?
My question is:
Assuming a platform where float
is a 32-bit IEEE754 floating point number (if that matters at all), does the C++ standard guarantee the expected memory layout for struct A
? If not, what does it guarantee and/or what are the ways to enforce the guarantees?
通过期望的内存布局,我的意思是该结构占用内存中的 16 * 4 = 64
个字节,每个连续的 4
个字节均被占用通过 data
数组中的单个 float
进行操作.换句话说,预期的内存布局意味着以下测试通过:
By the expected memory layout I mean that the struct takes up 16*4=64
bytes in memory, each consecutive 4
bytes occupied by a single float
from the data
array. In other words, expected memory layout means the following test passes:
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
是标准布局,请参见下文)
(offsetof
is legal here since A
is standard layout, see below)
万一这困扰您,测试实际上在带有gcc 9 HEAD的魔杖上通过了.我从未遇到过平台和编译器的结合,它们会提供证据证明该测试可能会失败,并且如果它们确实存在,我很想了解它们.
In case this bothers you, the test actually passes on wandbox with gcc 9 HEAD. I have never met a combination of a platform and compiler which would provide evidence that this test may fail, and I would love to learn about them in case they do exist.
- 类似SSE的优化需要某些内存布局(以及对齐方式,我可以在此问题中忽略它,因为可以使用标准的
alignas
说明符来处理它). - 对这种结构进行序列化可以简单地归结为一个美观且可移植的
write_bytes(& x,sizeof(A))
. - 某些API(例如OpenGL,特别是 glUniformMatrix4fv )期望使用这种确切的内存布局.当然,可以只将指针传递给
data
数组以传递这种类型的单个对象,但是对于其中的一系列序列(例如,用于上传矩阵类型的顶点属性),一种特定的内存布局是仍然需要.
- SSE-like optimizations require certain memory layout (and alignment, which I ignore in this question, since it can be dealt with using the standard
alignas
specifier). - Serialization of such a struct would simply boil down to a nice and portable
write_bytes(&x, sizeof(A))
. - Some APIs (e.g. OpenGL, specifically, say, glUniformMatrix4fv) expect this exact memory layout. Of course, one could just pass the pointer to
data
array to pass a single object of this type, but for a sequence of these (say, for uploading matrix-type vertex attributes) a specific memory layout is still needed.
据我所知,这些是可以从 struct A
中获得的东西:
These are the things that, to my knowledge, can be expected from struct A
:
- 这是标准布局
- 由于采用标准布局,因此指向
A
的指针可以被reinterpret_cast
指向指向其第一个数据成员的指针(大概是data [0]
吗?),即在第一个成员之前没有 填充.
- It is standard layout
- As a consequence of being standard-layout, a pointer to
A
can bereinterpret_cast
to a pointer to its first data member (which is, presumably,data[0]
?), i.e. there is no padding before the first member.
根据标准, 不是 (据我所知)剩下的两个保证是:
The two remaining guarantees that are not (as to my knowledge) provided by the standard are:
- 原始类型数组的元素之间没有填充 (我确信这是错误的,但是我找不到可确认的引用),
- 在
struct A
中的data
数组后没有填充 .
- There is no padding in between elements of an array of primitive type (I am sure that this is false, but I failed to find a confirmative reference),
- There is no padding after the
data
array insidestruct A
.
推荐答案
不保证布局的一件事是字节顺序,即多字节对象中字节的顺序. write_bytes(& x,sizeof(A))
不能在具有不同字节序的系统之间进行可移植的序列化.
One thing that is not guaranteed about the layout is endianness i.e. the order of bytes within a multi-byte object. write_bytes(&x, sizeof(A))
is not portable serialisation across systems with different endianness.
A
可以reinterpret_cast
指向其第一个数据成员的指针(大概是data [0]
吗?)>
A
can bereinterpret_cast
to a pointer to its first data member (which is, presumably,data[0]
?)
更正:第一个数据成员是 data
,您可以使用它重新解释演员.至关重要的是,数组不能与它的第一个元素进行指针互换,因此您不能重新解释它们之间的强制转换.但是,保证地址是相同的,据我所知,在 std :: launder
之后,重新解释为 data [0]
应该没问题.
Correction: The first data member is data
, which you can reinterpret cast with. And crucially, an array is not pointer-interconvertible with its first element, so you cannot reinterpret cast between them. The address however is guaranteed to be the same, so reinterpreting as data[0]
should be fine after std::launder
as far as I understand.
原始类型数组的元素之间没有填充
There is no padding in between elements of an array of primitive type
保证数组是连续的.对象的 sizeof
是根据将元素放置到数组中所需的填充指定的. sizeof(T [10])
的大小恰好为 sizeof(T)* 10
.如果相邻元素的非填充位之间存在填充,则该填充位于元素本身的末尾.
Arrays are guaranteed to be contiguous. sizeof
of an object is specified in terms of padding required to place elements into an array. sizeof(T[10])
has exactly the size sizeof(T) * 10
. If there is padding between non-padding bits of adjacent elements, then that padding is at the end of the element itself.
原始类型通常不能保证没有填充.例如,x86扩展精度 long double
是80位,填充为128位.
Primitive type is not guaranteed to not have padding in general. For example, the x86 extended precision long double
is 80 bits, padded to 128 bits.
char
,签名char
和 unsigned char
没有填充位.C标准(在这种情况下,C ++为其指定规范)保证固定宽度的 intN_t
和 uintN_t
别名没有填充位.在不可能的系统上,未提供这些固定宽度类型.
char
, signed char
and unsigned char
are guaranteed to not have padding bits. C standard (to which C++ delegates the specification in this case) guarantees that the fixed width intN_t
and uintN_t
aliases do not have padding bits. On systems where that is not possible, these fixed width types are not provided.
这篇关于带有原始类型的单个数组成员的标准布局结构的有保证的内存布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!