Неопределенное поведение внутри выражений void

Требуется ли реализация C для игнорирования неопределенного поведения, возникающего во время оценки выражений void, как если бы сама оценка никогда не проводилась?

Учитывая C11, 6.3.2.2 §1:

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

Это связано с общей идиомой, используемой для предотвращения предупреждений компиляторов о неиспользуемых переменных:

void f() {
  int a;
  (void)a;
}

Но что, если у нас есть неопределенное поведение, например:

void f() {
  int a;
  (void)(1/0);
}

Могу ли я с уверенностью утверждать, что эта программа не содержит неопределенного поведения? В стандарте сказано, что «его значение или обозначение отбрасывается», но «выражение (...) оценивается (...)», поэтому оценка действительно имеет место.

GCC / Clang сообщает о неопределенном поведении, поскольку в этом случае это очевидно, но в более тонком примере они этого не делают:

int main() {
  int a = 1;
  int b = 0;
  (void)(a/b);
  return 0;
}

Даже с -O0 ни GCC, ни Clang не оценивают 1/0. Но это также происходит даже без приведения к аннулированию, поэтому это не репрезентативно.

Доводя аргумент до крайности, не вызовет ли простая оценка (void)a в моем первом примере (где a неинициализировано) систематически неопределенное поведение?

В ISO C11 6.3.2.1 §2 упоминается, что:

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

Однако в Приложении J.2 Неопределенное поведение формулировка немного отличается:

Поведение не определено в следующих случаях:

(...)

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

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


person anol    schedule 24.07.2019    source источник
comment
Хм, даже (void)a находится в серой зоне. 6.3.2.2: Если выражение любого другого типа оценивается как недействительное, его значение или указатель отбрасывается. (Выражение void оценивается на предмет его побочных эффектов.)   -  person Antti Haapala    schedule 24.07.2019
comment
Абстрактная машина оценит выражение перед тем, как отбросить результат.   -  person Ian Abbott    schedule 24.07.2019
comment
Взгляните на это: https://www.godbolt.org/z/4tHKGg: на самом деле выражение вычисляется частично, то есть функция f вызывается три раза, но деление и сложение в выражении не оцениваются (потому что результат игнорируется). Но ИМО компилятор может сгенерировать код для деления и добавления, а затем проигнорировать результат.   -  person Jabberwocky    schedule 24.07.2019
comment
@IanAbbott Соответствующая реализация может выполнять оценку, но пункт 5.1.2.3§4 оставляет некоторую свободу: фактической реализации не нужно оценивать часть выражения, если она может сделать вывод, что его значение не используется и что никаких побочных эффектов не возникает.   -  person Virgile    schedule 24.07.2019
comment
Ответ на вопрос в первом предложении: «Можно ли игнорировать неопределенное поведение, возникающее во время оценки выражений void, как если бы сама оценка никогда не проводилась?», Несомненно, да. Стандарт C не предъявляет требований к неопределенному поведению, поэтому игнорирование неопределенного поведения является согласованным действием реализации C. Я не думаю, что это цель этого вопроса о переполнении стека - я думаю, что anol пытается спросить, может ли пользователь реализации полагаться на неопределенное поведение, не имеющее никакого эффекта; требуется реализация C, чтобы игнорировать это.…   -  person Eric Postpischil    schedule 24.07.2019
comment
@Virgule Реальная реализация могла оценивать ненужные части выражения в соответствии с абстрактной машиной, поэтому, если это неопределенное поведение для реальной машины, это неопределенное поведение - полная остановка.   -  person Ian Abbott    schedule 24.07.2019
comment
… Если да, то вы можете отредактировать первое предложение.   -  person Eric Postpischil    schedule 24.07.2019
comment
@EricPostpischil: действительно, спасибо за замечание, я его изменил. Если формулировка по-прежнему неверна, исправьте ее, чтобы она соответствовала тому, что вы поняли в своем комментарии (что я имел в виду).   -  person anol    schedule 24.07.2019
comment
UB может иметь неопределенные побочные эффекты :-)   -  person AndyG    schedule 24.07.2019
comment
@IanAbbott: да, пока вы говорите об абстрактной машине (в отличие от ее реализации), вы правы, оценка будет происходить. Я как-то неправильно прочитал ваш оригинальный комментарий, извините.   -  person Virgile    schedule 24.07.2019
comment
@Virgule На самом деле, в моем предыдущем комментарии была опечатка / мозг; Когда я писал так, что если он не определен для реальной машины, это полная остановка неопределенного поведения, я имел в виду абстрактную машину, а не реальную машину.   -  person Ian Abbott    schedule 24.07.2019


Ответы (1)


Это связано с общей идиомой, используемой для предотвращения предупреждений компиляторов о неиспользуемых переменных:

void f() {
  int a;
  (void)a;
}

Да и нет. Я бы сказал, что эта идиома превращает неиспользуемую переменную в используемую - она ​​появляется в выражении - с приведением к void, служащим для предотвращения жалоб компиляторов на результат неиспользования этого выражения. Но с технической точки зрения, языкового юриста, это конкретное выражение идиомы производит UB, потому что подвыражение a подлежит преобразованию lvalue, когда значение a не определено. Вы уже цитировали соответствующий текст стандарта.

Но что, если у нас есть неопределенное поведение, например:

void f() {
  int a;
  (void)(1/0);
}

Могу ли я с уверенностью утверждать, что эта программа не содержит неопределенного поведения?

No.

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

Да, точно так же, как выражение a в вашем предыдущем примере также оценивается, также производя UB. UB возникает из оценки внутреннего подвыражения. Преобразование в тип void является отдельным соображением, как и преобразование в любой другой тип.

GCC / Clang сообщает о неопределенном поведении, поскольку в этом случае это очевидно, но в более тонком примере они этого не делают:

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

Доводя аргумент до крайности, не вызовет ли простая оценка (void)a в моем первом примере (где a неинициализировано) систематически неопределенное поведение?

Да, как я уже заметил. Но это не означает, что программы, содержащие такие конструкции, обязаны неправильно вести себя. Что касается качества реализации, я считаю разумным надеяться, что оператор выражения (void)a; будет принят компилятором и вообще не будет иметь соответствующего поведения во время выполнения. Но я не могу полагаться на языковой стандарт, чтобы поддержать меня в этом.

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

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

person John Bollinger    schedule 24.07.2019