我遇到了一个 C++ 问题,在 Clang 编译的程序中实例化某些对象会出现段错误。预先感谢任何可以帮助阐明该问题的人。
调试表明 Clang 正在生成 SSE
movaps
指令来初始化一些 char 数组,而这些指令在某些情况下似乎会导致段错误。
我已经使用 Clang 16 和 Clang 17 的 binutils 链接器在多个 Linux 系统上进行了测试,结果相同。我不确定其他 x86-64 操作系统或其他链接器是否也会出现同样的问题。当使用 GCC 编译器版本而不是 Clang 时,不会出现此问题。 某些对象在以下最低条件下会发生段错误:
x86-64 代码生成
-n
--nmagic
) 链接器选项链接以关闭 ELF 部分的页面对齐
$ clang -O1 -nostdlib -fno-stack-protector -Wl,-n -static clang_segv.s clang_segv.cc -o clang_segv
clang_segv.cc:
struct SegV
{
void set(const char *s) { char *b = buf; while ( *s ) { *b++ = *s++; } *b = '\0'; }
char buf[128] = "";
char *cursor = buf; // needed for segfault
};
int
main()
{
SegV v;
v.set("aa");
return 0;
}
clang_segv.s
.intel_syntax noprefix
.global _start
_start:
xor rbp,rbp # Zero stack base pointer
xor r9,r9
pop rdi # Pop argc off stack -> rdi for 1st arg to main()
mov rsi,rsp # Argv @top of stack -> rsi for 2nd arg to main()
call main # Call main()... return result ends up in rax
xor r9,r9
mov rdi,rax # Move main()'s return to 1st argument for exit()
mov rax,231 # exit_group() syscall
syscall # Tell kernel to exit program
这个例子是我能想到的最小重现器,与原始代码没有相似之处,我注意到问题在于两者都有带有 char 数组的对象。更改代码可以掩盖或揭露问题,这通常表明存在编码错误,但我在这个简单的示例中找不到一个错误。
我的调试器似乎认为问题出在 Clang 生成的初始化“buf”字符数组的指令上:
。 我的调试器说 Clang 生成以下代码来初始化
char buf[128]
:
0x400171 xorps %xmm0,%xmm0
0x400174 movaps %xmm0,-0x10(%rsp)
0x400179 movaps %xmm0,-0x20(%rsp)
0x40017e movaps %xmm0,-0x30(%rsp)
0x400183 movaps %xmm0,-0x40(%rsp)
0x400188 movaps %xmm0,-0x50(%rsp)
0x40018d movaps %xmm0,-0x60(%rsp)
0x400192 movaps %xmm0,-0x70(%rsp)
0x400197 movaps %xmm0,-0x80(%rsp)
0x40019c lea -0x80(%rsp),%rax
并且段错误是由第一条 movapps 指令生成的。
显然,我希望数组的初始化不应该出现段错误。
无论是像我在本示例中所做的那样使用类内初始化还是使用初始化器列表初始化都没有关系。两种方法都遇到同样的问题。
我认为问题可能是由 clang 生成的代码“认为”对象的成员(应该)对齐与成员“实际”对齐的方式不匹配。我可能是错的,但如果我将alignas(32) 添加到结构或字符数组中,问题就会消失。我不知道为什么需要以 32 字节对齐。令人困惑的是(对我来说),按 16 字节对齐并不能掩盖问题。
如果我只是告诉 Clang 不要使用 -mno-sse
生成任何 SSE 指令,问题也会消失,但我不想失去这些优化。在这种情况下,Clang 使用 movq
指令来初始化数组,而不是
movaps
。
如果我放弃 char 数组成员的初始化并在构造函数中手动执行此操作,问题也会消失,但这当然效率较低。在这一点上,对我来说,这看起来像是一个编译器错误。还是我只是用错了?谢谢!
与 -n (--nmagic) 链接器选项链接以关闭 ELF 部分的页面对齐
不Clang(相当合理)假设它正在处理这样做?
标准
链接器行为,它尊重所需的对象对齐。
当您使用特殊标志告诉链接器不要这样做时,您就得靠自己了。
在我看来,这看起来像是一个编译器错误。
不是的。如果您将其报告给 Clang 开发人员,他们很可能会因为“不要这样做”用户错误而将其关闭。
如果我只是告诉 Clang 不要使用 -mno-sse 生成任何 SSE 指令,问题也会消失,但我不想失去这些优化。
无法保证
Clang 所做的其他假设不会导致其他地方崩溃。如您所见,优化与
-nmagic