Почему выпадение из конца непустой функции без возврата значения не приводит к ошибке компилятора?
С тех пор, как я понял много лет назад, что это не приводит к ошибке по умолчанию (по крайней мере, в GCC), я всегда задавался вопросом, почему?
Я понимаю, что вы можете использовать флаги компилятора для выдачи предупреждения, но не должно ли это быть ошибкой? Почему имеет смысл, чтобы не пустая функция не возвращала значение, чтобы быть действительным?
Пример в соответствии с просьбой в комментариях:
#include <stdio.h>
int stringSize()
{
}
int main()
{
char cstring[5];
printf( "the last char is: %c\n", cstring[stringSize()-1] );
return 0;
}
... компилирует.
11 ответов
Стандарты C99 и C++ не требуют, чтобы функции возвращали значение. Отсутствующий оператор возврата в функции, возвращающей значение, будет определен (для возврата 0
) только в main
функция.
Обоснование состоит в том, что проверка того, возвращает ли каждый путь кода значение, довольно сложна, и возвращаемое значение может быть установлено с помощью встроенного ассемблера или других хитрых методов.
Из C++11 черновик:
§ 6.6.3 / 2
Вытекающее из конца функции [...] приводит к неопределенному поведению в функции, возвращающей значение.
§ 3.6.1 / 5
Если контроль достигает конца
main
не встречаяreturn
утверждение, эффект заключается в выполненииreturn 0;
Обратите внимание, что поведение, описанное в C++ 6.6.3/2, отличается от поведения в C.
gcc выдаст вам предупреждение, если вы вызовете его с опцией -Wreturn-type.
Wreturn-type Предупреждать всякий раз, когда функция определена с типом возврата, по умолчанию int. Также предупредите о любом операторе возврата без возвращаемого значения в функции, чей тип возврата не является пустым (падение конца тела функции считается возвращаемым без значения), а также об операторе возврата с выражением в функции, Возвращаемый тип void.
Это предупреждение включено -Wall.
Просто для любопытства посмотрите, что делает этот код:
#include <iostream>
int foo() {
int a = 5;
int b = a + 1;
}
int main() { std::cout << foo() << std::endl; } // may print 6
Этот код имеет формально неопределенное поведение, и на практике он вызывает соглашение и архитектуру. В одной конкретной системе с одним конкретным компилятором возвращаемое значение является результатом последней оценки выражения, сохраненной в eax
регистр процессора этой системы.
По умолчанию gcc не проверяет, что все пути кода возвращают значение, потому что в общем случае это невозможно. Предполагается, что вы знаете, что делаете. Рассмотрим общий пример с использованием перечислений:
Color getColor(Suit suit) {
switch (suit) {
case HEARTS: case DIAMONDS: return RED;
case SPADES: case CLUBS: return BLACK;
}
// Error, no return?
}
Вы, программист, знаете, что, за исключением ошибки, этот метод всегда возвращает цвет. gcc верит, что вы знаете, что делаете, поэтому не заставляет вас помещать возврат в конец функции.
Javac, с другой стороны, пытается проверить, что все пути кода возвращают значение, и выдает ошибку, если не может доказать, что они все делают. Эта ошибка предписана спецификацией языка Java. Обратите внимание, что иногда это неправильно, и вы должны добавить ненужное выражение return.
char getChoice() {
int ch = read();
if (ch == -1 || ch == 'q') {
System.exit(0);
}
else {
return (char) ch;
}
// Cannot reach here, but still an error.
}
Это философская разница. C и C++ являются более разрешающими и доверчивыми языками, чем Java или C#, поэтому некоторые ошибки в более новых языках являются предупреждениями в C/C++, а некоторые предупреждения игнорируются или отключаются по умолчанию.
Вы имеете в виду, почему выпадающий из конца функции, возвращающей значение (т.е. выход без явного return
) это не ошибка?
Во-первых, в C вопрос о том, возвращает ли функция что-либо значимое или нет, имеет значение только в том случае, если исполняемый код действительно использует возвращаемое значение. Возможно, язык не хотел заставлять вас возвращать что-либо, когда вы знаете, что вы не собираетесь использовать его в большинстве случаев.
Во-вторых, очевидно, что спецификация языка не хотела заставлять авторов компилятора обнаруживать и проверять все возможные пути управления на наличие явного return
(хотя во многих случаях это не так сложно сделать). Кроме того, некоторые пути управления могут привести к невозвратным функциям - черта, которая обычно не известна компилятору. Такие пути могут стать источником досадных ложных срабатываний.
Отметим также, что C и C++ различаются в своих определениях поведения в этом случае. В C++ просто выпадение из конца функции, возвращающей значение, всегда является неопределенным поведением (независимо от того, используется ли результат функции вызывающим кодом). В Си это вызывает неопределенное поведение, только если вызывающий код пытается использовать возвращаемое значение.
The language rule is that if the closing
}
of a function that returns a non- value is reached and the caller attempts to use that value, the behavior is undefined. Just falling off the end of the function has well defined behavior as long as the caller doesn't use the value.
It would be possible to require all possible control paths to execute a
return
statement before leaving the function, but C traditionally has not required compilers to do that kind of code analysis. (Many compilers will do that analysis anyway and issue a warning if appropriate.)
The main reason for allowing falling off the end of a non- function is historical. K&R C (the version described in the 1978 first edition of Kernighan and Ritchie's book, before the 1989 ANSI and 1990 ISO C standard) did not have the keyword or type. And prior to the 1999 ISO C standard, C had the "implicit " rule, meaning that you could declare or define a function without an explicit return type and it would return an result.
In K&R C, if you wanted a function that didn't return a result, you would define it without an explicit return type and simply not return a value:
#include <stdio.h>
do_something() {
printf("Not returning a value\n");
}
int main() {
do_something();
return 0;
}
The function would actually return some garbage value which the caller would quietly ignore.
In modern C, you would write:
#include <stdio.h>
void do_something(void) {
printf("Not returning a value\n");
}
int main(void) {
do_something();
}
which guarantees that the caller can't try to use the returned value. As of C89/C90, the language still supported the old style to avoid breaking existing code. When the implicit rule was dropped in C99, the requirements on non-
void
functions failing to return a value were not changed (and most C99 and later compilers still support the implicit
int
rule by default, probably with a warning, so old K&R C code can still be compiled).
Под C/C++ разрешено не возвращаться из функции, которая утверждает, что возвращает что-то. Есть несколько вариантов использования, таких как вызов exit(-1)
или функция, которая вызывает его или выдает исключение.
Компилятор не собирается отказываться от легального C++, даже если он ведет к UB, если вы просите об этом. В частности, вы просите об отсутствии предупреждений. (Gcc по-прежнему включает некоторые по умолчанию, но когда они добавляются, они, похоже, соответствуют новым функциям, а не новым предупреждениям для старых функций)
Изменение по умолчанию no-arg gcc для выдачи некоторых предупреждений может быть серьезным изменением для существующих скриптов или систем. Хорошо продуманные либо -Wall
и обрабатывать предупреждения или переключать отдельные предупреждения.
Обучение использованию цепочки инструментов C++ является препятствием для обучения программированию на C++, но цепочки инструментов C++ обычно пишутся экспертами и для них.
Я считаю, что это из-за унаследованного кода (C никогда не требовал оператора return, как и C++) Вероятно, существует огромная кодовая база, опирающаяся на эту "функцию". Но, по крайней мере, есть -Werror=return-type
флаг на многих компиляторах (включая gcc и clang).
В некоторых ограниченных и редких случаях может быть полезным переход от конца непустой функции без возврата значения. Как следующий код, специфичный для MSVC:
double pi()
{
__asm fldpi
}
Эта функция возвращает число пи с использованием сборки x86. В отличие от сборки в GCC, я не знаю, как использоватьreturn
чтобы сделать это без дополнительных затрат на результат.
Насколько мне известно, основные компиляторы C++ должны выдавать как минимум предупреждения о явно недействительном коде. Если я сделаю тело изpi()
пусто, GCC/Clang выдаст предупреждение, а MSVC сообщит об ошибке.
Люди упоминали исключения и exit
в некоторых ответах. Это не веские причины. Либо выбросить исключение, либо вызватьexit
, не приведет к прерыванию выполнения функции. И компиляторы это знают: написание оператора throw или вызовexit
в пустом теле pi()
остановит любые предупреждения или ошибки от компилятора.
При каких обстоятельствах это не приводит к ошибке? Если он объявляет тип возвращаемого значения и ничего не возвращает, для меня это звучит как ошибка.
Единственное исключение, о котором я могу подумать, это main()
функция, которая не нуждается в return
утверждение вообще (по крайней мере, в C++; у меня нет под рукой ни одного из стандартов C). Если нет возврата, он будет действовать так, как если бы return 0;
это последнее утверждение.
Похоже, вам нужно включить предупреждения вашего компилятора:
$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function ‘main’:
<stdin>:1: warning: ‘return’ with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$
Я получил это предупреждение, потому что забыл добавить оператор itr = itr ->currentNode; в основном отсутствует этот оператор, функция попадает в цикл infinte и никогда не возвращала значение.Это причина, по которой я получал это предупреждение во время компиляции
void* list_get(list* lst, int idx){
node* itr = lst->head;
if (idx >= lst->size){
printf("list out of index");
exit(1);
}
while(itr != NULL){
if(itr->index == idx){
return itr->element;
}
itr = itr->currentNode;
}
}
Это нарушение ограничения в c99, но не в c89. Контраст:
c89:
3.6.6.4
return
заявлениеОграничения
return
оператор с выражением не должен появляться в функции, тип возвращаемойvoid
,
c99:
6.8.6.4
return
заявлениеОграничения
return
оператор с выражением не должен появляться в функции, тип возвращаемойvoid
,return
оператор без выражения должен появляться только в функции, тип возвращаемойvoid
,
Даже в --std=c99
В режиме gcc выдает только предупреждение (хотя без необходимости включать дополнительные -W
флажки, как требуется по умолчанию или в c89/90).
Изменить, чтобы добавить это в c89, "достигнув }
которая завершает функцию эквивалентно выполнению return
"Выражение без выражения" (3.6.6.4). Однако в c99 поведение не определено (6.9.1).