Почему происходит отключение функции не-void без возврата значения, не приводящего к ошибке компилятора?

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

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

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

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

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

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

Ответ 1

Стандарты C99 и С++ не требуют функций для возврата значения. Недопустимый оператор return в возвращающей значение функции будет определен (возвращать 0) только в функции main.

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

Из С++ 11 черновик:

§ 6.6.3/2

Отбрасывание конца функции [...] приводит к поведению undefined в возвращающей значение функции.

§ 3.6.1/5

Если элемент управления достигает конца main, не встречая оператора return, эффект заключается в выполнении

return 0;

Обратите внимание, что поведение, описанное в С++ 6.6.3/2, не совпадает с C.


gcc выдаст вам предупреждение, если вы вызываете его с параметром -Wreturn-type.

-Wreturn-type Предупреждать, когда функция определена с помощью возвращаемого типа, который по умолчанию - int. Также предупреждайте о любых return без возвращаемого значения в функции, возврат которой не является пустота (падающая с конца Тело функции считается возвратным без значения), и о возврате с выражением в функция, тип возврата которой недействителен.

Это предупреждение включено -Wall.


Как любопытство, посмотрите, что делает этот код:

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

Этот код имеет формальное поведение undefined, и на практике это соглашение о вызовах и архитектура. В одной конкретной системе с одним конкретным компилятором возвращаемое значение является результатом оценки последнего выражения, которое хранится в регистре eax этого системного процессора.

Ответ 2

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 и С++ являются более разрешительными и доверчивыми языками, чем Java или С#, поэтому некоторые ошибки на более новых языках являются предупреждениями на C/С++, а некоторые предупреждения игнорируются или отключены по умолчанию.

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

double pi()
{
    __asm fldpi
}

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

Насколько я знаю, основные компиляторы C++ должны выдавать как минимум предупреждения для явно некорректного кода. Если я сделаю тело pi() пустым, GCC/Clang сообщит о предупреждении, а MSVC сообщит об ошибке.

Люди упоминали исключения и exit в некоторых ответах. Это не веские причины. Если вы сгенерируете исключение или вызовете exit, не заставит выполнение функции завершиться. И компиляторы знают это: запись оператора throw или вызов exit в пустом теле pi() остановит любые предупреждения или ошибки от компилятора.

Ответ 6

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

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

Ответ 7

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

$ 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
$

Ответ 8

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

Ответ 9

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

c89:

3.6.6.4 Оператор return

Ограничения

Оператор

A return с выражением не должен появляться в функция, тип возврата которой void.

c99:

6.8.6.4 Оператор return

Ограничения

Оператор

A return с выражением не должен появляться в функции, тип возврата которой void. Оператор return без выражения должен появляться только в функции, тип возврата которой void.

Даже в режиме --std=c99 gcc выдаст предупреждение (хотя без необходимости добавления дополнительных флагов -W, как это требуется по умолчанию или в c89/90).

Изменить, чтобы добавить, что в c89, "достижение }, которое завершает функцию, эквивалентно выполняя инструкцию return без выражения "(3.6.6.4). Однако в c99 поведение undefined (6.9.1).