tl;dr: функциям C++ не требуется ключевое словоreturn, чтобы они были корректно сформированы и компилировались, хотя это, скорее всего, приведет к неопределенному поведению. Ваш конкретный компилятор также может выдать предупреждение или ошибку. Почти во всех случаях вам понадобится ключевое словоreturn.

C++, язык, печально известный своей способностью падать и не компилироваться при малейших ошибках, имеет то, что на первый взгляд кажется огромным упущением в спецификации: непустая функция не требует ключевого слова return для получения компилируемого кода. код.

Компиляция и запуск через GCC 4.6.3 дает следующий результат:

Ясно, что он заметил проблему и показал предупреждение, но все равно пошел дальше и создал исполняемый исполняемый файл.

Так почему же это так? И какие побочные эффекты это имеет?

Ключевое слово return никогда не определяется явно, как это требуется для функции.

Проще говоря, нигде в спецификации C++ не сказано, что функция должна иметь ключевое слово return для создания компилируемого кода. Однако в нем говорится, что любая функция с непустым возвращаемым типом, которая достигает последней строки (где была бы ваша закрывающая фигурная скобка), будет производить неопределенное поведение.

Неопределенное поведение может быть чем угодно — программа может вылететь, она может вернуть мусорные значения (это наиболее вероятно!), программа может даже попытаться совершить переворот и попытаться силой свергнуть местную власть. Все зависит от вашего компилятора.

Мы рассмотрим конкретную формулировку позже, а пока давайте рассмотрим влияние, которое она оказывает на компиляторы.

Все функции возвращают значение, так почему бы просто не применить этот факт во время компиляции? 👷

Потому что не все функции обязательно возвращаются.

function5, строго говоря, никогда не возвращается. Вместо этого выбрасывается исключение, стек раскручивается, и мы идем вверх, чтобы найти функцию, которую можно поймать. return 1; никогда не достигается.

Точно так же function6 завершает работу программы до того, как return 2.

Все это кажется глупым. Почему тогда компилятор просто не проверяет эти случаи? Не так уж сложно убедиться, что все функции возвращаются, выбрасывают или завершают работу правильно?

Можете ли вы гарантировать, что function7 когда-нибудь вернётся? Все, что он делает, это ждет, пока пользователь введет какой-либо ввод. Что, если пользователь никогда не прикасается к своей клавиатуре? Откуда компилятору знать?

А как насчет function8? Мы, как программисты, знаем, что rand() никогда не вернет число больше 1, но компилятор может этого не знать, особенно если rand включен из какого-то неизвестного, внешняя библиотека.

Это связано с проблемой остановки, давней проблемой информатики. Он спрашивает в очень упрощенной форме:

Имея некоторую функцию A и ввод, можем ли мы всегда написать другую функцию B, которая сообщает нам, завершится ли A или будет работать вечно?

Чтобы сэкономить вам 200 лет компьютерных доказательств, ответ: нет, вы не можете.

В результате, если бы спецификация C++ гарантировала, что все функции должны иметь действительные ключевые слова возврата во всех возможных путях для всех функций, это было бы невозможно реализовать. Независимо от того, сколько пограничных случаев вы пытаетесь исправить, проблема остановки гарантирует, что всегда будет какая-то функция, для которой вы не сможете вычислить полный набор путей возврата.

Оставив это как неопределенное поведение, разные компиляторы могут выполнять разумный (но не обязательно исчерпывающий все функции) анализ и выдавать предупреждения/ошибки там, где, по их мнению, должно существовать return ключевых слов.

Так что же на самом деле говорит спецификация C++? 👓

Здесь мы немного углубимся в технические детали, однако здесь нет ничего, что мы еще не рассмотрели. Согласно [stmt.return]:

Выход из конца конструктора, деструктора или функции с возвращаемым типом cv void эквивалентен возврату без операнда. В противном случае вытекание из конца функции, отличной от main, приводит к неопределенному поведению.

Не волнуйтесь, если эта очень конкретная и юридическая формулировка отталкивает — давайте рассмотрим ее вместе.

Спецификация C++ определяет оператор return в этом разделе, [stmt.return], и делает различные ссылки на него в остальной части спецификации. Однако нигде в спецификации не сказано, что функции требуется ключевое слово return для создания правильного кода. Правильно сформированный код — это в своей простейшей форме код, который будет успешно скомпилирован.

Значит ли это, что игнорировать returnключевые слова абсолютно безопасно? Нет.

Стекание конца конструктора, деструктора или функции, возвращающей cv void, равносильно добавлению return; в последнюю строку. Под cv void просто подразумевается функция с типом возвращаемого значения void, которая может быть const или volatile. (Это различие между const и volatile не имеет значения.)

Вытекание означает достижение последней строки (где была бы закрывающая фигурная скобка), где больше не следует кода.

Таким образом, следующие две функции идентичны:

Обратите внимание, что спецификация определяет только неявное добавление return for void возвращающих функций, конструкторов и деструкторов.

Что касается вытекания из конца непустой функции, это приводит к неопределенному поведению. Это означает достижение конца функции без нажатия другого оператора перехода для выхода, где оператор перехода может быть одним из break, continue, return или goto.

Что насчет главного? Мне сказали, что нормально не иметь ключевого слова return в main! 🙋‍

И это совершенно верно!

Согласно [basic.start.main],

Если управление передается с конца compound-statement main, эффект эквивалентен возврату с операндом 0 (см. также [except.handle]).

В спецификации явно указано, что отсутствие ключевого слова return в вашей основнойфункции неявно добавит ключевое слово return 0;.

Что вынести из этой статьи

Из этого нет никакого конкретного кода или программирования — я бы определенно не рекомендовал использовать эту причуду в вашем коде где бы то ни было. Надеюсь, это вдохновит вас на более глубокое изучение того, как работает C++, и вы воспользуетесь возможностью взглянуть на спецификацию.

Дайте мне знать в комментариях, если вы нашли это интересным, если у вас есть что добавить, или если вы считаете, что я допустил какие-либо ошибки в своем объяснении проблемы остановки!

Ваше здоровье!

Некоторые дополнительные ссылки для чтения:

https://stackoverflow.com/questions/3402178/omiting-return-statement-in-c



https://stackoverflow.com/questions/1610030/why-does-flowing-off-the-end-of-a-non-void-function-without-returning-a-value-no