Странное поведение Клана - программирование
Подтвердить что ты не робот

Странное поведение Клана

Посмотрите на этот фрагмент кода:

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

Какие-либо предложения о том, что происходит не так?

Спасибо заранее!

4b9b3361

Ответ 1

Это, скорее всего, ошибка в clang 3.2. Я не могу воспроизвести крушение с помощью створки.

Ответ 2

Да, это ошибка в Clang++. Я могу воспроизвести его с помощью CLang 3.2 в i386-pc-linux-gnu.

А теперь некоторый случайный анализ...

Я обнаружил, что ошибка заключается в преобразовании из labmda в pointer-to-function: компилятор создает своего рода thunk с соответствующей сигнатурой, которая вызывает лямбда, но имеет инструкцию ud2 вместо ret.

Инструкция ud2, как вы, наверное, знаете, является инструкцией, которая явно вызывает исключение "Invalid Opcode". То есть инструкция намеренно оставила undefined.

Взгляните на дизассемблирование: это функция 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. Тогда сгенерированный 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 в отчаянии.