看一下这段代码:
#include <iostream>
#include <string>
void foo(int(*f)()) {
std::cout << f() << std::endl;
}
void foo(std::string(*f)()) {
std::string s = f();
std::cout << s << std::endl;
}
int main() {
auto bar = [] () -> std::string {
return std::string("bla");
};
foo(bar);
return 0;
}
使用
进行编译g++ -o test test.cpp -std=c++11
导致:
bla
像它应该做的那样。用编译它
clang++ -o test test.cpp -std=c++11 -stdlib=libc++
导致:
zsh: illegal hardware instruction ./test
用
编译clang++ -o test test.cpp -std=c++11 -stdlib=stdlibc++
也导致:
zsh: illegal hardware instruction ./test
Clang / GCC版本:
clang version 3.2 (tags/RELEASE_32/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
gcc version 4.7.2 (Gentoo 4.7.2-r1 p1.5, pie-0.5.5)
有人提出任何建议是什么问题吗?
提前致谢!
答案 0 :(得分:11)
是的,这是Clang ++中的一个错误。我可以用i386-pc-linux-gnu中的CLang 3.2重现它。
现在进行一些随机分析......
我发现错误是从labmda转换为指向函数的指针:编译器使用调用lambda的相应签名创建一种 thunk ,但它具有指示ud2
代替ret
。
您可能都知道,指令ud2
是一条明确提出“无效操作码”异常的指令。也就是说,一条指令故意未定义。
看看反汇编:这是thunk函数:
main::$_0::__invoke():
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
movl %ecx, 4(%esp)
calll main::$_0::operator()() const ; this calls to the real lambda
subl $4, %esp
ud2 ; <<<-- What the...!!!
所以这个bug的最小例子就是:
int main() {
std::string(*f)() = [] () -> std::string {
return "bla";
};
f();
return 0;
}
奇怪的是,如果返回类型是一个简单类型,例如int
,则不会发生错误。然后生成的 thunk 是:
main::$_0::__invoke():
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl %eax, (%esp)
calll main::$_0::operator()() const
addl $8, %esp
popl %ebp
ret
我怀疑问题在于转发返回值。如果它适合寄存器,例如eax
一切顺利。但是如果它是一个很大的结构,比如std::string
它会在堆栈中返回,那么编译器就会感到困惑并在绝望中发出ud2
。
答案 1 :(得分:5)
这很可能是clang 3.2中的一个错误。我不能用clang trunk重现崩溃。