读取/写入部分分配的对齐内存
Read / write partially allocated aligned memory
有很多 关于访问未分配内存的问题,这显然是未定义的行为。但是下面的极端情况呢。
考虑以下结构,它对齐到 16 个字节,但只占用其中的 8 个字节:
struct alignas(16) A
{
float data[2]; // the remaining 8 bytes are unallocated
};
现在我们通过 SSE 对齐加载/存储内在函数访问 16 个字节的数据:
__m128 test_load(const A &a)
{
return _mm_load_ps(a.data);
}
void test_store(A &a, __m128 v)
{
_mm_store_ps(a.data, v);
}
这也是未定义的行为吗?我应该改用填充吗?
无论如何,由于英特尔内在函数不是标准 C++,访问部分分配但对齐的内存块(不大于对齐的大小)是标准 C++ 中的未定义行为吗?
我同时解决了固有情况和标准 C++ 情况。我对他们两个都很感兴趣。
语言律师对此的回答是'the question is moot'。 _mm_load_ps
在标准中没有定义,它使用的是标准中也没有定义的ASM指令。 C++ 不处理这个。
至于你的第二个问题——以这种方式从 C++ 访问未分配的内存显然是未定义的行为。此内存中未放置任何对象,因此您无法访问它。
另见这个问题的阅读部分基本上是那个问题的重复。
它是符合 ISO C++ 标准的 UB,但我认为像这样的只读访问在提供英特尔内在函数(可自由定义他们想要的任何额外行为)。它在 asm 中绝对是安全的,但风险在于,优化将 UB 转换为错误编译代码的 C++ 编译器可能会导致问题,如果它们能够证明那里没有任何内容可读的话。在链接的问题上有一些讨论。
在对象之外写入总是不好的。不要这样做,即使你放回你之前读过的相同垃圾也不行:非原子 load/store 对可能是一个问题,这取决于你的结构后面有什么数据。
唯一可以的情况是在一个数组中,您知道接下来会发生什么,并且有未使用的填充。例如使用 16B 存储重叠 4B 写出 3-float
结构数组。 (没有 alignas
过度对齐,所以数组将它们打包在一起而没有填充)。
3 float
的结构比 2 floats
更好。
对于这个具体示例(2 个浮点数),您可以只使用 MOVSD 执行 64 位零扩展加载,并使用 MOVSD 或 MOVLPS 执行 [=14 的低半部分的 64 位存储=].
有很多 关于访问未分配内存的问题,这显然是未定义的行为。但是下面的极端情况呢。
考虑以下结构,它对齐到 16 个字节,但只占用其中的 8 个字节:
struct alignas(16) A
{
float data[2]; // the remaining 8 bytes are unallocated
};
现在我们通过 SSE 对齐加载/存储内在函数访问 16 个字节的数据:
__m128 test_load(const A &a)
{
return _mm_load_ps(a.data);
}
void test_store(A &a, __m128 v)
{
_mm_store_ps(a.data, v);
}
这也是未定义的行为吗?我应该改用填充吗?
无论如何,由于英特尔内在函数不是标准 C++,访问部分分配但对齐的内存块(不大于对齐的大小)是标准 C++ 中的未定义行为吗?
我同时解决了固有情况和标准 C++ 情况。我对他们两个都很感兴趣。
语言律师对此的回答是'the question is moot'。 _mm_load_ps
在标准中没有定义,它使用的是标准中也没有定义的ASM指令。 C++ 不处理这个。
至于你的第二个问题——以这种方式从 C++ 访问未分配的内存显然是未定义的行为。此内存中未放置任何对象,因此您无法访问它。
另见
它是符合 ISO C++ 标准的 UB,但我认为像这样的只读访问在提供英特尔内在函数(可自由定义他们想要的任何额外行为)。它在 asm 中绝对是安全的,但风险在于,优化将 UB 转换为错误编译代码的 C++ 编译器可能会导致问题,如果它们能够证明那里没有任何内容可读的话。在链接的问题上有一些讨论。
在对象之外写入总是不好的。不要这样做,即使你放回你之前读过的相同垃圾也不行:非原子 load/store 对可能是一个问题,这取决于你的结构后面有什么数据。
唯一可以的情况是在一个数组中,您知道接下来会发生什么,并且有未使用的填充。例如使用 16B 存储重叠 4B 写出 3-float
结构数组。 (没有 alignas
过度对齐,所以数组将它们打包在一起而没有填充)。
3 float
的结构比 2 floats
更好。
对于这个具体示例(2 个浮点数),您可以只使用 MOVSD 执行 64 位零扩展加载,并使用 MOVSD 或 MOVLPS 执行 [=14 的低半部分的 64 位存储=].