Что происходит, когда функция, возвращающая объект, заканчивается без оператора return

В С++, что происходит, когда функция, которая должна возвращать объект, заканчивается без оператора return? Что возвращается?

например.

std::string func() {}

Ответы

Ответ 1

Что возвращается?

Мы не знаем. Согласно стандарту, поведение undefined.

$6.6.3/2 Оператор return [Stmt.return]:

(акцент мой)

Отбрасывание конца конструктора, деструктора или функции с возвращаемым типом cv void эквивалентно return без операнда. В противном случае при выходе из функции, отличной от main (basic.start.main), вы получите undefined поведение.

Фактически большинство компиляторов дадут предупреждение для него, например Clang:

предупреждение: управление достигает конца не-void функции [-Wreturn-type]

Ответ 2

В С++, что происходит, когда функция, которая должна возвращать объект, заканчивается без оператора return?

Он вызывает поведение undefined. Никто не может сказать, что именно произойдет.

Ответ 3

Мне было любопытно, поэтому я сделал несколько тестов на Visual С++ 2015.

int f()
{
    if (false)
        return 42;

    // oops
}

int main()
{
    int i = f();
}

Мне пришлось добавить if, чтобы получить предупреждение вместо жесткой ошибки:

> cl /nologo /FAs /c a.cpp
a.cpp(6) : warning C4715: 'f': not all control paths return a value

Сгенерированный код сборки довольно прост, и я удалил ненужные части. Здесь мясо f():

f:
    xor eax, eax
    je label
    mov eax, 42
label:
    ret

Строка xor в основном eax=0. Поскольку if (false) является постоянным условием, сгенерированный код даже не пытается выполнить сравнение, а затем будет безоговорочно переходить на label, который просто возвращается из функции. Вы можете видеть, что "возвращаемое значение" (42) действительно будет храниться в eax, но эта строка никогда не будет выполнена. Поэтому eax == 0.

Здесь main() делает:

    call f
    mov _i$[ebp], eax
    ret

Он вызывает f() и слепо копирует eax в место в стеке (где i есть). Поэтому i == 0.

Попробуйте что-то более сложное с объектом и конструктором:

struct S { int i=42; };

S f()
{
    if (false)
        return {};

    // oops
}

int main()
{
    S s = f();
}

Что main() делает в основном резерв sizeof(S) байтов в стеке, поместите адрес первого байта в eax, а затем вызовите f():

    lea eax, _s$[ebp]
    push eax
    call f

Опять же, f() ничего не сделает, поскольку он безоговорочно перейдет к концу функции:

f:
    xor eax, eax
    je label
    ; some stuff
    ; call S::S(), which would set i to 42
    ; but none of that will happen
label:
    ret

Итак, что случилось с байтами sizeof(S) в главном? Они никогда не менялись. Они содержат то, что уже было в памяти в этом конкретном месте. Они содержат мусор.

Это с неоптимизированной сборкой в ​​данной версии данного компилятора. Измените компилятор, измените его. Включить оптимизатор резко изменить поведение.

Не делай этого.