Почему этот компилятор кода С++ (функция non-void не возвращает значение)

Я нашел это в одной из моих библиотек сегодня утром:

static tvec4 Min(const tvec4& a, const tvec4& b, tvec4& out)
{
    tvec3::Min(a,b,out);
    out.w = min(a.w,b.w);
}

Я ожидаю ошибку компилятора, потому что этот метод ничего не возвращает, а тип возврата не является void.

Единственная вещь, которая приходит на ум, - это

  • В единственном месте, где вызывается этот метод, возвращаемое значение не используется или не сохраняется. (Этот метод должен был быть void - возвращаемый тип tvec4 является ошибкой копирования и вставки)

  • создается созданная по умолчанию конструкция tvec4, которая кажется немного не похожей, а, все остальное на С++.

Я не нашел часть спецификации С++, которая обращается к этому. Ссылки (га) оценены.

Обновление

В некоторых случаях это создает ошибку в VS2012. Я не сузил специфику, но это интересно, тем не менее.

Ответ 1

Это undefined поведение в разделе проекта С++ 11 6.6.3 return statement paragraph 2, который гласит:

[...] Выключение конца функции эквивалентно возврату без значения; это приводит к поведению undefined в возвращающей значение функции. [...]

Это означает, что компилятор не обязан предоставлять ошибку или предупреждение, потому что во всех случаях это может быть трудно диагностировать. Это мы видим из определения поведения undefined в черновом проекте в разделе 1.3.24, в котором говорится:

[...] Допустимое поведение undefined варьируется от полного игнорирования ситуации с непредсказуемыми результатами, поведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей диагностического сообщения или без него) для прекращения перевода или выполнения (с выдачей диагностического сообщения). [...]

Хотя в этом случае мы можем получить как gcc, так и clang для генерации wanring с использованием флага -Wall, который дает мне предупреждение, подобное этому:

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

Мы можем превратить это конкретное предупреждение в ошибку, используя флаг -Werror=return-type. Мне также нравится использовать -Wextra -Wconversion -pedantic для своих личных проектов.

Как упоминает ComicSansMS в Visual Studio, этот код будет генерировать C4716, который по умолчанию является ошибкой, сообщение, которое я вижу, это:

ошибка C4716: "Мин": необходимо вернуть значение

и в случае, когда не все пути кода возвращают значение, тогда он генерирует C4715, что является предупреждением.

Ответ 2

Возможно, какая-то разработка вопроса о том, почему часть вопроса:

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

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

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

†: В общем случае это эквивалентно проблеме остановки, поэтому на самом деле невозможно, чтобы машина решила это надежно.

Ответ 3

Скомпилируйте свой код с помощью опции -Wreturn-type:

$ g++ -Wreturn-type source.cpp

Это даст вам предупреждение. Вы можете включить предупреждение , если вы также используете -Werror:

$ g++ -Wreturn-type -Werror source.cpp

Обратите внимание, что это превратит все предупреждения в ошибки. Поэтому, если вам нужна ошибка для конкретного предупреждения, скажем -Wreturn-type, просто введите return-type без -W как:

$ g++ -Werror=return-type source.cpp

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

Ответ 4

Может быть, какая-то дополнительная доработка о том, почему часть вопроса.

С++ был спроектирован таким образом, что очень большой массив уже существующего тела кода C компилируется с минимальным количеством изменений. К сожалению, сам C выполнял аналогичную обязанность перед самым ранним предварительным стандартом C, у которого даже не было ключевого слова void, и вместо этого полагался на возвращаемый по умолчанию тип int. Функции C обычно возвращали значения, и всякий раз, когда код, внешне похожий на алгоритмы Algol/Pascal/Basic, был написан без каких-либо операторов return, функция находилась под капотом, возвращая какой бы мусор остался в стеке. Ни вызывающий, ни вызывающий не назначают ценность мусора надежным способом. Если мусор игнорируется каждым абонентом, все в порядке, а С++ наследует моральное обязательство скомпилировать такой код.

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

Неявный int является лишь частью используемого здесь наследия C. Функция "диспетчер" может, в зависимости от параметра, возвращать различные типы из некоторых ветвей кода и не возвращать полезное значение из других ветвей кода. Такая функция, как правило, должна быть объявлена ​​для возврата типа, достаточно длинного для хранения любого из возможных типов, и вызывающему может потребоваться отбросить его или извлечь из union.

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

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

Ответ 5

Как уже упоминалось, это поведение undefined и даст вам предупреждение о компиляторе. В большинстве мест, в которых я работал, вы должны включить параметры компилятора для обработки предупреждений как ошибок, что гарантирует, что весь ваш код должен скомпилироваться с 0 ошибками и 0 предупреждениями. Это хороший пример того, почему это хорошая идея.

Ответ 6

Это больше стандартное правило/свойство С++, которое имеет тенденцию быть гибким с вещами и имеет тенденцию быть ближе к C.

Но когда мы говорим о компиляторах, GCC или VS, они больше подходят для профессионального использования и для различных целей разработки и, следовательно, содержат более строгие правила разработки в соответствии с вашими потребностями.

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

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

Ответ 7

Наряду с этим есть еще несколько вопросов, связанных с этим поведением возврата результата без инструкции return. Один простой пример:

int foo(int a, int b){ int c = a+b;}

int main(){
   int c = 5;
   int d = 5;

   printf("f(%d,%d) is %d\n", c, d, foo(c,d)); 

   return 0;
}

Может ли эта аномалия быть вызвана свойствами стека и более конкретно:

Машины с нулевым адресом

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

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

Примечание:

с этим ответом я хотел бы обсудить одно возможное объяснение странного поведения на уровне машины (инструкции), поскольку он уже имеет контекст и охватывает его в достаточно широком диапазоне.