2013-04-03 194 views
14

看一看这段代码:奇怪锵行为

#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 

锵/ 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) 

任何有什么建议是什么问题?

在此先感谢!

+9

我会说这是叮铛中的一个错误 – 2013-04-03 21:30:39

+0

仅供参考,有关[ud2和clang]的更多信息(http://stackoverflow.com/q/26309300/1708801) – 2015-03-19 14:47:25

回答

5

这很可能是叮当3.2中的一个错误。我无法用铿锵树干重现崩溃。

+0

肯定的。树干似乎工作正常。所以它可能应该在3.3版中修复。 – rralf 2013-04-04 10:01:22

11

是的,这是Clang ++中的一个错误。我可以在i386-pc-linux-gnu中用CLang 3.2重现它。

现在一些随机分析...

我发现的bug是从labmda转换为指针到函数:编译器创建一种与适当的签名调用lambda,但它的指令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...!!! 

因此,错误的小例子将是简单的:

int main() { 
    std::string(*f)() = []() -> std::string { 
     return "bla"; 
    }; 
    f(); 
    return 0; 
} 

奇怪的是,该错误不会如果返回类型是简单类型,例如int,则会发生。然后将生成的是:

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