unaligned access store on intel processor
考虑下面的示例。它在标记行出现 gcc 5.4 的段错误时
我用 g++ -O3 -std=c++11 编译它。它在指令 movaps 处失败,我怀疑它执行未对齐的内存访问。可能是 gcc 为这样一个简单的示例生成了非法代码,还是我遗漏了一些东西?
我在 Intel i5-5200U 上运行它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
#include <vector>
#include <memory>
#include <cstdint>
using namespace std;
__attribute__ ((noinline)) void SerializeTo(const vector<uint64_t>& v, uint8_t* dest) { for (size_t i = 0; i < v.size(); ++i) { *reinterpret_cast<uint64_t*>(dest) = v[i]; // Segfaults here. dest += sizeof(uint64_t); } }
int main() { std::vector<uint64_t> d(64);
unique_ptr<uint8_t[]> tmp(new uint8_t[1024]);
SerializeTo(d, tmp.get() + 6);
return 0; } |
- 您可以使用 g++ -S -O2 -fverbose-asm 查看生成的代码
- 使用 -O2 它不会生成矢量化代码。
- 然后将 -O2 替换为您想要的任何优化,例如-O3 -march=native
- 然后它会出现段错误。我不跟随
- 阅读有关未定义行为的更多信息,尤其是 Lattner 的博客。并且更加信任编译器:它比您的代码经过更多测试。请首先责怪您的代码,而不是编译器。所以确实,你错过了一些东西。也尝试其他编译器(例如 Clang…)和版本。启用所有警告(使用 -Wall -Wextra)
- 非常感谢@BasileStarynkevitch。我也相信更多的编译器。这就是为什么我在这里寻求建议的原因。你的不是很有帮助:)
您在数组中步进了 6 个字节,所以它现在未对齐。编译器无法知道它必须避免需要对齐的指令;这就是类型双关语是未定义行为的原因。
- 是的,它没有对齐。我认为如果不能确定地址是否对齐,编译器应该生成可以处理未对齐地址的代码。你不同意吗?
-
编译器”知道”地址是对齐的,因为所有使此类指针的有效方法都会产生对齐的地址。 (这里,”知道”意味着”可以假设,因为替代方案是结果无关紧要的未定义行为”。)
-
@Roman:即使数据已对齐,当您违反严格别名时,编译器也没有义务生成有效代码。 (这是未定义的行为)
-
我不同意。上次我检查时,我可以写入属于我的进程的任意内存位置。 SerializeTo 也没有内联,因此它不能假设 dest 是对齐的。想象一下 memcpy 会突然决定它的参数应该对齐。这是胡说八道
-
@AndyG 我怎么会违反严格的别名?我认为强制转换为 uint8_t 是有效的?
-
@Roman:内核和处理器将允许您使用对其有效的指令写入任何内存位置。但是你的牛肉是编译器(又名 C ),而不是操作系统。 C 说你不能这样做,现在你知道为什么了。 memcpy 需要 void*,所以它当然不知道有什么对齐要求。
-
@DavisHerring我不明白这是什么意思”C说你不能这样做,现在你知道为什么了。”。并且用 “void*” 替换 dest 的类型当然不能解决它
-
Roman:如果您想了解更多信息,请参阅标准中的部分(例如,您可以免费找到的 N4296 草案)下 [basic.lval]通过以下类型之一以外的泛左值的对象行为未定义”
-
@Roman:C 不是汇编程序。编译器非常有用地使用高性能指令,而您要求的不仅仅是 “-O3 please”。这样做的代价是你必须遵守语言的规则,否则坏事就会发生,正如你所见。我没有说”使用 void*“;我说 memcpy 有更宽松的要求是有原因的。
-
@AndyG 谢谢,你是对的。我想如果其中一种类型是 (unsigned) char 那么双向转换是安全的。显然只有 1 个方向是有效的。
在 c 中合法地执行类型双关的方法很少。
魔术函数 std::memcpy 是这里选择的工具:
1 2 3 4 5 6 7
|
__attribute__ ((noinline))
void SerializeTo(const vector<uint64_t>& v, uint8_t* dest) {
for (size_t i = 0; i < v.size(); ++i) {
std::memcpy(dest, std::addressof(v[i]), sizeof(v[i]));
dest += sizeof(uint64_t);
}
}
|
结果输出与 -std=c++11 -O3 -march=native -Wall -pedantic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
SerializeTo(std::vector<unsigned long, std::allocator<unsigned long> > const&, unsigned char*): # @SerializeTo(std::vector<unsigned long, std::allocator<unsigned long> > const&, unsigned char*)
mov rax, qword ptr [rdi]
cmp qword ptr [rdi + 8], rax
je .LBB0_3
xor ecx, ecx
.LBB0_2: # =>This Inner Loop Header: Depth=1
mov rax, qword ptr [rax + 8*rcx]
mov qword ptr [rsi + 8*rcx], rax
add rcx, 1
mov rax, qword ptr [rdi]
mov rdx, qword ptr [rdi + 8]
sub rdx, rax
sar rdx, 3
cmp rcx, rdx
jb .LBB0_2
.LBB0_3:
ret
|
https://godbolt.org/g/ReGA9N
-
谢谢理查兹。那么下面的解决方案呢? void SerializeTo(const vector<uint64_t>
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/268854.html