Является ли выражение с поведением undefined, которое никогда не выполняется на самом деле, делает программу ошибочной?

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

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

#include <iostream>

int main()
{
    int n = 0;
    if (false)
      n=n++;   // Undefined behaviour if it gets executed, which it doesn't
    std::cout << "Hi there.\n";
}

Для ясности, Я предполагаю, что программа хорошо сформирована (поэтому, в частности, UB не связан с предварительной обработкой). На самом деле я готов ограничить UB, связанный с "оценками", которые явно не являются объектами компиляции. Определения, относящиеся к приведенному примеру, я думаю, (акцент мой):

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

Вычисления значений операндов оператор упорядочивается перед вычислением значения результата оператора. Если побочный эффект на скалярном объекте не зависит от... или вычисления значения с использованием значения одного и того же скалярного объекта, поведение undefined.

Неявно ясно, что субъекты в последнем предложении, "побочный эффект" и "вычисление значений" являются примерами "оценки", так как это определение "упорядочено до" определено для.

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

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

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

Некоторые связанные указатели на этом сайте:

Ответ 1

Если побочный эффект скалярного объекта не зависит от etc

Побочные эффекты - это изменения состояния среды исполнения (1.9/12). Изменение - это изменение, а не выражение, которое, если оно оценивается, потенциально может привести к изменению. Если изменений нет, побочный эффект отсутствует. Если побочного эффекта нет, то никакой побочный эффект не имеет никакого отношения к чему-либо еще.

Это не означает, что любой код, который никогда не выполняется, является UB-бесплатным (хотя я уверен, что большая часть его). Каждое возникновение UB в стандарте должно рассматриваться отдельно. (Исчерпанный текст, вероятно, слишком осторожен, см. Ниже).

В стандарте также говорится, что

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

(акцент мой)

Это, насколько я могу судить, является единственной нормативной ссылкой, в которой говорится, что выражение "undefined поведение" означает: undefined операция при выполнении программы. Нет выполнения, нет UB.

Ответ 2

Нет. Пример:

struct T {
    void f() { }
};
int main() {
    T *t = nullptr;
    if (t) {
        t->f(); // UB if t == nullptr but since the code tested against that
    }
}

Ответ 3

Решение о том, будет ли программа выполнять целочисленное деление на 0 (это UB), в общем случае эквивалентно задаче остановки. Компилятор вообще не может определить это, в общем. Таким образом, простое присутствие возможного UB не может логически повлиять на остальную часть программы: требование об этом в стандарте потребует от каждого поставщика компилятора обеспечения решения проблемы с остановкой в ​​компиляторе.

Еще проще, следующая программа имеет UB только в том случае, если пользователь вводит 0:

#include <iostream>
using namespace std;

auto main() -> int
{
    int x;
    if( cin >> x ) cout << 100/x << endl;
}

Было бы абсурдно утверждать, что эта программа сама по себе имеет UB.

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

Ответ 4

В общем случае лучшее, что мы можем сказать здесь, это то, что это зависит.

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

[Пример:

int f(bool b) {
  unsigned char c;
  unsigned char d = c; // OK, d has an indeterminate value
  int e = d;           // undefined behavior
  return b ? d : 0;    // undefined behavior if b is true
}

- конец примера]

так что эта строка кода:

return b ? d : 0;

является только undefined, если b - true. Кажется, это интуитивный подход, и, похоже, Джон видит его также, если мы читаем Его время, чтобы получить серьезную информацию об использовании undefined Behavior.

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

constexpr const char *str = "Hello World" ;      

constexpr char access()
{
    return str[100] ;
}

int main()
{
}

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

Ответ 5

Существует четкое различие между врожденным поведением undefined, таким как n = n ++, и кодом, который может определять поведение или undefined в зависимости от состояния программы во время выполнения, например x/y для int. В последнем случае программа должна работать, если y равно 0, но в первом случае компилятор попросил генерировать код, который полностью нелегитимен - он в пределах своих прав отказаться от компиляции, он может просто не быть "пуленепробированным" против такого и, следовательно, его состояние оптимизатора (распределения регистров, записи о том, какие значения могут быть изменены после чтения и т.д.), повреждаются, что приводит к фиктивным машинным кодам для этого и окружающего исходного кода. Возможно, ранний анализ распознал ситуацию "a = b ++" и сгенерировал код для предыдущего, если перепрыгнуть через двухбайтную инструкцию, но когда n = n ++ встречается, никакая команда не выводится, так что оператор if прыгает где-то в следующие коды операций. Во всяком случае, это просто игра. Помещение "если" спереди или даже упаковка его другой функцией не документируется как "содержащее" поведение undefined... биты кода не испорчены поведением undefined - стандарт последовательно говорит "программа имеет поведение undefined".

Ответ 6

Это должно быть, если не "должно".

Поведение по определению из ISO C (соответствующее определение не найдено в ISO С++, но оно должно быть как-то применимо):

3.4

1 поведение

внешний вид или действие

И UB:

WG21/N4527

1.3.25 [defns.undefined]

undefined поведение

для которого настоящий международный стандарт не предъявляет требований [Примечание: поведение Undefined может ожидаться, если в этом международном стандарте отсутствует явное определение поведения или когда программа использует ошибочную конструкцию или ошибочные данные. Допустимое поведение Undefined варьируется от полного игнорирования ситуации с непредсказуемыми результатами, ведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей диагностического сообщения или без него), до прекращения перевода или выполнения (с выдача диагностического сообщения). Многие ошибочные программные конструкции не порождают поведение Undefined; они должны быть диагностированы. -end note]

Несмотря на "вести себя во время перевода" выше, слово "поведение", используемое ISO С++, в основном касается выполнения программ.

WG21/N4527

1.9 Выполнение программы [intro.execution]

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

2 Некоторые аспекты и операции абстрактной машины описаны в этом Международном стандарте в качестве реализации (например, sizeof(int)). Они составляют параметры абстрактной машины. Каждая реализация должна включать в себя документацию, описывающую ее характеристики и поведение в этих отношениях .6 Такая документация должна определять экземпляр абстрактной машины, которая соответствует этой реализации (именуемая "соответствующий экземпляр" ниже).

3 Некоторые другие аспекты и операции абстрактной машины описаны в этом Международном стандарте как неопределенные (например, оценка выражений в new-initializer, если функция распределения не выделяет память (5.3.4)). Там, где это возможно, настоящий международный стандарт определяет набор допустимых видов поведения. Они определяют недетерминированные аспекты абстрактной машины. Таким образом, экземпляр абстрактной машины может иметь более одного возможного выполнения для данной программы и заданного ввода.

4 Некоторые другие операции описаны в этом Международном стандарте как Undefined (например, эффект попытки изменить объект const). [Примечание. Этот международный стандарт не устанавливает требований к поведению программ, содержащих поведение Undefined. -end note]

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

5) Это положение иногда называют правилом "как есть", поскольку реализация может игнорировать любое требование настоящего международного стандарта, если результат будет таким, как если бы это требование выполнялось, насколько это возможно определяется по наблюдаемому поведению программы. Например, фактическая реализация не должна оценивать часть выражения, если она может вывести, что ее значение не используется и что никаких побочных эффектов, влияющих на наблюдаемое поведение программы, не производится.

6) В эту документацию также включены условно поддерживаемые конструкции и поведение, зависящее от локали. См. 1.4.

Понятно, что поведение Undefined было вызвано конкретной конструкцией языка, используемой неправильно или не переносимым образом (что не соответствует стандарту). Однако в стандарте не упоминается ничего о том, какая конкретная часть кода в программе вызовет его. Другими словами, "с Undefined поведением" является свойством (о соответствии) всей исполняемой программы, а не ее меньшими частями.

Стандарт мог бы дать более сильную гарантию, чтобы сделать поведение корректным, как только какой-то конкретный код не выполняется, только тогда, когда существует способ точно сопоставить код С++ с соответствующим поведением. Это сложно (если не невозможно) без детальной семантической модели о выполнении. Короче говоря, операционная семантика, заданная приведенной выше моделью абстрактной машины, недостаточна для достижения более сильной гарантии. Но в любом случае ISO С++ никогда не будет JVMS или ECMA-335. И я не ожидаю, что будет полный набор формальной семантики, описывающей язык.

Ключевой проблемой здесь является смысл "исполнения". Некоторые люди думают, что "выполнение программы" означает запуск программы. Это не совсем так. Обратите внимание, что представление программы, выполняемой в абстрактной машине, не указывается. (Также обратите внимание, что "этот международный стандарт не требует от структуры соответствия реализации".) Исполняемый здесь код может быть буквально кодом С++ (не обязательно машинным кодом или некоторыми другими формами промежуточного кода, который вообще не указан стандартом). Это эффективно позволяет использовать основной язык в качестве интерпретатора, онлайн-частичного оценщика или некоторых других монстров, которые переводят код С++ на лету. В результате на самом деле нет возможности полностью разделить фазы перевода (определенные ISO С++ [lex.phases]) полностью перед процессом выполнения без знания конкретных реализаций. Таким образом, необходимо разрешить UB, возникающее во время трансляции, когда слишком сложно указать переносимое четко определенное поведение.

Помимо вышеперечисленных проблем, возможно, для большинства обычных пользователей достаточно одной (нетехнической) причины: просто не нужно предоставлять более сильную гарантию, разрешать плохой код и побеждать один из (вероятнейших наиболее важных) аспектов полезности Сам UB: поощрять быстро отбрасывать некоторый (излишне) непереносимый вонючий код без усилий, чтобы "исправить" их, что в конечном итоге будет напрасно.

Дополнительные примечания:

Некоторые слова копируются и восстанавливаются из одного из моих ответов на этот комментарий.

Ответ 7

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

Например:

void maybe_launch_missiles(void)
{      
  if (should_launch_missiles())
  {
    arm_missiles();
    if (should_launch_missiles())
      launch_missiles();
  }
  disarm_missiles();
}
int foo(int x)
{
  maybe_launch_missiles();
  return x<<1;
}

В соответствии с текущим стандартом C, если компилятор мог определить, что disarm_missiles() всегда будет возвращаться без завершения, но три другие внешние функции, которые вызывают выше, могут завершиться, самая эффективная стандартно-совместимая замена для оператора foo(-1); (return значение игнорируется) будет should_launch_missiles(); arm_missiles(); should_launch_missiles(); launch_missiles();.

Поведение программы будет определяться только в том случае, если вызов should_launch_missiles() завершается без возврата, если первый вызов возвращает ненулевое значение и arm_missiles() завершается без возврата, или если оба вызова возвращают ненулевые и launch_missiles() завершается без возвращение. Программа, которая работает правильно в этих случаях, будет придерживаться стандарта независимо от того, что она делает в любой другой ситуации. Если возврат из maybe_launch_missiles() приведет к Undefined Behavior, компилятору не потребуется распознавать возможность того, что любой вызов should_launch_missiles() может вернуть ноль.

Как следствие, некоторые современные компиляторы, эффект смещения левого отрицательного числа может быть хуже всего, что может быть вызвано любым типом Undefined Поведение на типичном компиляторе C99 на платформах, которые разделяют пространства кода и данных и переполнение стека ловушек. Даже если код задействован в Undefined Behavior, который может вызвать случайную передачу управления, не было бы никаких средств, с помощью которых он мог бы вызывать последовательно arm_missiles() и launch_missiles() без промежуточного вызова disarm_missiles(), если хотя бы один вызов should_launch_missiles() возвращает ненулевое значение. Однако гипермодерный компилятор может отрицать такие защиты.

Ответ 8

В контексте критически важной для безопасности встроенной системы опубликованный код будет считаться дефектным:

  • Код не должен проходить проверку кода и/или соответствие стандартам (MISRA и т.д.).
  • Статический анализ (lint, cppcheck и т.д.) должен отмечать это как дефект
  • Некоторые компиляторы могут помечать это как предупреждение (подразумевая также дефект.)