具有原始类型的单个数组成员的标准布局结构的保证的内存布局

时间:2019-04-12 11:38:07

标签: c++ language-lawyer memory-layout standard-layout

考虑以下简单结构:

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。我从未遇到过平台和编译器的结合,它们会提供证据证明该测试可能会失败,并且我很想了解它们的存在。

为什么还要关心:

  • 类似SSE的优化需要某些内存布局(和对齐方式,我可以在此问题中忽略它,因为可以使用标准alignas说明符来处理它)。
  • 对这种结构进行序列化可以简单地归结为一个美观且可移植的write_bytes(&x, sizeof(A))
  • 某些API(例如OpenGL,特别是glUniformMatrix4fv)期望使用这种确切的内存布局。当然,可以只将指针传递到data数组以传递该类型的单个对象,但是对于其中的一系列对象(例如,用于上传矩阵类型的顶点属性),仍然需要特定的内存布局。

实际上可以保证的是

据我所知,这些是struct A可以预期的:

  • standard layout
  • 作为标准布局的结果,指向A的指针可以reinterpret_cast指向其第一个数据成员(大概是data[0]?)的指针,即第一个成员之前 之前没有填充。

根据标准, 不是 (据我所知)剩下的两个保证是:

  • 原始类型数组的元素之间没有填充 (我确信这是错误的,但是我找不到可确认的引用),< / li>
  • data中的struct A数组后没有填充

2 个答案:

答案 0 :(得分:12)

不能保证布局的一件事是字节顺序,即多字节对象中字节的顺序。 write_bytes(&x, sizeof(A))不是跨不同字节序的系统的可移植序列化。

  

A可以reinterpret_cast指向它的第一个数据成员的指针(大概是data[0]吗?)

更正:第一个数据成员是data,您可以使用其重新解释演员。至关重要的是,数组不能与第一个元素进行指针互换,因此您不能重新解释它们之间的强制转换。但是,地址肯定是相同的,据我所知,在data[0]之后重新解释为std::launder应该没问题。

  

原始类型数组的元素之间没有填充

保证数组是连续的。根据将元素放置到数组所需的填充来指定对象的sizeofsizeof(T[10])的大小恰好为sizeof(T * 10)。如果相邻元素的非填充位之间存在填充,则该填充位于元素本身的末尾。

原始类型通常不能保证没有填充。例如,x86扩展精度long double是80位,填充为128位。

charsigned charunsigned char确保没有填充位。 C标准(在这种情况下,C ++为其指定规范)保证固定宽度intN_tuintN_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));