Почему выпадение из конца непустой функции без возврата значения не приводит к ошибке компилятора? - PullRequest
155 голосов
/ 23 октября 2009

С тех пор, как я понял много лет назад, что это не выдает ошибку по умолчанию (по крайней мере, в GCC), я всегда задавался вопросом, почему?

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

Пример по запросу в комментариях:

#include <stdio.h>
int stringSize()
{
}

int main()
{
    char cstring[5];
    printf( "the last char is: %c\n", cstring[stringSize()-1] ); 
    return 0;
}

... компилирует.

Ответы [ 8 ]

144 голосов
/ 23 октября 2009

Стандарты 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 процессора этой системы.

42 голосов
/ 23 октября 2009

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. Обратите внимание, что иногда это неправильно, и вы должны добавить ненужный оператор возврата.

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 ++, а некоторые предупреждения игнорируются или отключаются по умолчанию.

13 голосов
/ 23 октября 2009

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

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

Во-вторых, очевидно, языковая спецификация не хотела заставлять авторов компилятора обнаруживать и проверять все возможные пути управления на наличие явного return (хотя во многих случаях это не так сложно сделать). Кроме того, некоторые пути управления могут привести к невозвратным функциям - черта, которая обычно не известна компилятору. Такие пути могут стать источником досадных ложных срабатываний.

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

5 голосов
/ 28 мая 2016

В C / C ++ разрешено не возвращаться из функции, которая утверждает, что возвращает что-то. Существует ряд вариантов использования, таких как вызов exit(-1) или функция, которая вызывает его или выдает исключение.

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

Изменение стандартного no-arg gcc для выдачи некоторых предупреждений может быть критическим изменением для существующих скриптов или систем. Хорошо продуманные из них либо -Wall и имеют дело с предупреждениями, либо переключают отдельные предупреждения.

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

0 голосов
/ 16 апреля 2015

Я считаю, что это из-за устаревшего кода (C никогда не требовал оператора return, как и C ++) Вероятно, существует огромная кодовая база, опирающаяся на эту «функцию». Но, по крайней мере, есть -Werror=return-type флаг на многих компиляторах (включая gcc и clang).

0 голосов
/ 23 октября 2009

Это нарушение ограничения в c99, но не в c89. Контраст:

c89:

3.6.6.4 return оператор

Ограничения

A 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).

0 голосов
/ 23 октября 2009

Похоже, вам нужно включить предупреждения вашего компилятора:

$ 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
$
0 голосов
/ 23 октября 2009

При каких обстоятельствах не выдает ошибку? Если он объявляет тип возвращаемого значения и ничего не возвращает, для меня это звучит как ошибка.

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

...