Подтвердить что ты не робот

Возвращаются ли все функции в C/С++?

Я читал эту статью в отношении undefined, и один из примеров "оптимизаций" выглядит очень сомнительным:

if (arg2 == 0)
    ereport(ERROR, (errcode(ERRCODE_DIVISION_BY_ZERO),
                    errmsg("division by zero")));
/* No overflow is possible */
PG_RETURN_INT32((int32) arg1 / arg2);

Рисунок 2: неожиданная оптимизация исключает деление на ноль проверьте, в src/backend/utils/adt/int8.c PostgreSQL. Призыв к ereport(ERROR, :::) вызовет исключение.

По сути, компилятор предполагает, что ereport вернется и удалит проверку arg2 == 0, поскольку наличие деления подразумевает ненулевой знаменатель, т.е. arg2 != 0.

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

EDIT: все зависит от ereport, который описан таким образом:

   84 /*----------
   85  * New-style error reporting API: to be used in this way:
   86  *      ereport(ERROR,
   87  *              (errcode(ERRCODE_UNDEFINED_CURSOR),
   88  *               errmsg("portal \"%s\" not found", stmt->portalname),
   89  *               ... other errxxx() fields as needed ...));
   90  *
   91  * The error level is required, and so is a primary error message (errmsg
   92  * or errmsg_internal).  All else is optional.  errcode() defaults to
   93  * ERRCODE_INTERNAL_ERROR if elevel is ERROR or more, ERRCODE_WARNING
   94  * if elevel is WARNING, or ERRCODE_SUCCESSFUL_COMPLETION if elevel is
   95  * NOTICE or below.
   96  *
   97  * ereport_domain() allows a message domain to be specified, for modules that
   98  * wish to use a different message catalog from the backend's.  To avoid having
   99  * one copy of the default text domain per .o file, we define it as NULL here
  100  * and have errstart insert the default text domain.  Modules can either use
  101  * ereport_domain() directly, or preferably they can override the TEXTDOMAIN
  102  * macro.
  103  *
  104  * If elevel >= ERROR, the call will not return; we try to inform the compiler
  105  * of that via pg_unreachable().  However, no useful optimization effect is
  106  * obtained unless the compiler sees elevel as a compile-time constant, else
  107  * we're just adding code bloat.  So, if __builtin_constant_p is available,
  108  * use that to cause the second if() to vanish completely for non-constant
  109  * cases.  We avoid using a local variable because it not necessary and
  110  * prevents gcc from making the unreachability deduction at optlevel -O0.
  111  *----------
4b9b3361

Ответ 1

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

В C или С++ не разрешено компилятором оптимизировать на этой основе, если только он не знает о том, что ereport возвращает (например, путем вставки его и проверки кода).

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

В документе говорится:

Однако программист не смог сообщить компилятору, что вызов ereport (ERROR,:) не возвращается.

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

К сожалению, довольно сложно доказать, что преобразование кода неверно, ссылаясь на стандарт, поскольку я не могу процитировать ничего, чтобы показать, что его нет, спрятано где-то на страницах 700-900, небольшое предложение, в котором говорится: о, кстати, все функции должны возвращаться ". Я на самом деле не читал каждую строку стандарта, но такое положение было бы абсурдным: функции должны быть разрешены для вызова abort() или exit() или longjmp(). В С++ они также могут генерировать исключения. И им должно быть позволено делать это условно - атрибут noreturn означает, что функция никогда не возвращается, а не то, что она не может вернуться, а ее отсутствие ничего не доказывает, возвращается ли функция или нет. Мой опыт обоих стандартов заключается в том, что они не абсурдны.

Оптимизациям не разрешено разбить действительные программы, они ограничены правилом "как есть", чтобы наблюдаемое поведение сохранялось. Если ereport не возвращается, тогда "оптимизация" изменяет наблюдаемое поведение программы (от того, что делает ereport вместо того, чтобы возвращаться, чтобы иметь undefined поведение из-за деления на ноль). Следовательно, это запрещено.

Здесь больше информации по этому вопросу:

http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=616180

В нем упоминается отчет об ошибке GCC http://gcc.gnu.org/bugzilla/show_bug.cgi?id=29968, который был (по праву IMO) отклонен, но если ereport не возвращается, проблема PostGreSQL не является то же, что и отклоненный отчет об ошибке GCC.

В описании ошибки debian указано следующее:

Ребята из gcc полны этого. Здесь актуальна проблема C стандартное определение точек последовательности, и, в частности, требование, чтобы видимые побочные эффекты более позднего утверждения не могли произойдет до выполнения более раннего вызова функции. Последний время я приставал к ним по этому поводу, у меня появилось какое-то хромое утверждение, что SIGFPE не было побочным эффектом в определениях спецификации. При этом момент полезного обсуждения прекратился, поскольку невозможно договориться с кем-то, кто хочет это утверждать.

Фактически, если более поздний оператор имеет UB, то в стандарте явно указано, что вся программа имеет UB. У Бен есть цитата в его ответе. Дело не в этом (как кажется, кажется, этот человек), что все видимые побочные эффекты должны происходить до последней точки последовательности перед UB. UB разрешает изобретать машину времени (и более прозаично, она позволяет вывести из строя исполнение, предполагающее, что все выполненное определяет поведение). Парни gcc не полны этого, если все, что они говорят.

SIGFPE будет видимым побочным эффектом, если компилятор решит гарантировать и документировать (как расширение к стандарту), что он встречается, но если это просто результат UB, то это не так. Сравните, например, параметр -fwrapv с GCC, который изменяет переполнение целых чисел из UB (что говорит стандарт) на обертку (что гарантирует компилятор, только если вы укажете опцию). В MIPS gcc имеет опцию -mcheck-zero-division, которая выглядит так, как она определяет поведение при делении на ноль, но я никогда не использовал его.

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

Мы обнаружили семь подобных проблем в PostgreSQL, которые были отмечены как "GCC ошибок" в комментариях к исходному коду

Но функция, не возвращающаяся, сильно отличается от функции, возвращающейся после некоторых побочных эффектов. Если он не возвращается, оператор, который будет иметь UB, не будет выполняться в определении абстрактной машины C (или С++) в стандарте. Неохваченные заявления не выполняются: Я надеюсь, что это не спорное. Поэтому, если "gcc guys" должны были заявить, что UB из неохваченных операторов отображает всю программу undefined, тогда они будут полны. Я не знаю, что они заявили об этом, и в конце отчета Debian есть предложение о том, что этот вопрос, возможно, ушел GCC 4.4. Если да, то, возможно, PostGreSQL действительно столкнулся с признанной ошибкой, а не (как автор статьи, которую вы ссылаетесь на мысли), действительная оптимизация или (как человек, который говорит, что gcc-парни полны его мысли), неверное истолкование стандартом авторов GCC.

Ответ 2

Я думаю, что ответ найден, по крайней мере для С++, в разделе 1.9p5

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

Фактически, макрос расширяется до вызова errstart, который вернет (ERROR >= ERROR), очевидно, true. Это вызывает вызов errfinish, который вызывает proc_exit, который запускает некоторую зарегистрированную очистку, а затем стандартную функцию выполнения exit. Таким образом, невозможно выполнить выполнение, которое содержит деление на ноль. Однако проверка логики компилятора должна быть ошибочной. Или, возможно, более ранняя версия кода не смогла нормально выйти.

Ответ 3

Мне кажется, что если компилятор не может доказать, что ereport() не вызывает exit() или abort() или какой-либо другой механизм для завершения программы, эта оптимизация недействительна. В стандарте языка упоминаются несколько механизмов для завершения и даже определяется "нормальное" завершение программы путем возврата из main() в терминах функции exit().

Не говоря уже о том, что прекращение программы не требуется, чтобы избежать выражения разделения. for (;;) {} отлично действует C.

Ответ 4

Нет, в новейшем C-стандарте C11 есть даже новое ключевое слово, чтобы указать, что функция не вернется, _Noreturn.

Ответ 5

В документе не говорится, что проверка if (arg2 == 0) удалена. В нем говорится, что деление перемещается перед проверкой.

Цитирование paper:

... GCC перемещает деление до нулевой проверки arg2 == 0, вызывая деление на ноль.

Результат тот же, но рассуждения разные.

Если компилятор считает, что ereport вернется, тогда он "знает", что деление будет выполняться во всех случаях. Кроме того, if-statement не влияет на аргументы деления. И, очевидно, разделение не влияет на if-statement. И хотя вызов ereport может иметь наблюдаемые побочные эффекты, деление не (если мы игнорируем какое-либо исключение по отдельности).

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

Один из способов взглянуть на это состоит в том, что поведение undefined включает перемещение во времени.; -)

Я бы сказал, что поведение undefined (например, деление на 0) должно рассматриваться как наблюдаемое поведение. Это предотвратит это переупорядочение, потому что наблюдаемое поведение деления должно происходить после наблюдаемого поведения вызова ereport. Но я не пишу стандартов или компиляторов.

Ответ 6

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

Например, общий алгоритм состоит в том, чтобы иметь непрерывный цикл в main() (a.k.a. фоновый цикл), и вся функциональность имеет место в ISR (процедура обслуживания прерываний).

Другим примером являются задачи RTOS. В нашем встроенном системном проекте у нас есть задачи, которые находятся в цикле infinte: Pend on queue message, process message, repeat. Они сделают это для жизни проекта.

Некоторые встроенные системы имеют безопасные петли выключения, где они помещают машину в безопасное состояние, блокируют все пользовательские входные данные и ждут отключения питания или reset.

Кроме того, некоторые встроенные системы могут завершить работу системы. Выключение питания не позволяет системе вернуться.

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

Ответ 7

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

__attribute__ ((noreturn)) делает это для gcc.