带有原始类型的单个数组成员的标准布局结构的有保证的内存布局 [英] Guaranteed memory layout for standard layout struct with a single array member of primitive type

查看:74
本文介绍了带有原始类型的单个数组成员的标准布局结构的有保证的内存布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下简单结构:

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 be reinterpret_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 inside struct 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 be reinterpret_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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆