奇怪的Clang行为

Strange Clang behaviour

本文关键字:行为 Clang      更新时间:2023-10-16

看一下这段代码:

#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) 

有谁能告诉我出了什么问题吗?

提前感谢!

是的,这是clang++中的一个bug。我可以在i386-pc-linux-gnu中使用CLang 3.2复制它。

现在是一些随机分析…

我发现这个错误是在从labmda到指针到函数的转换中:编译器创建了一种具有适当签名的thunk,它调用lambda,但它的指令是ud2而不是ret

指令ud2,你可能都知道,是一个显式引发"无效操作码"异常的指令。也就是说,一条指令故意没有定义。

看一下反汇编:这是一个笨重的函数:

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

这很可能是clang 3.2中的一个bug。我不能用clang trunk重现崩溃