Неопределенное, неопределенное и определяемое реализацией поведение

В чем разница между неопределенным, неопределенным и определяемым реализацией поведением в C и C++?

10 ответов

Решение

Неопределенное поведение - это один из аспектов языка C и C++, который может удивить программистов, пришедших из других языков (другие языки пытаются скрыть это лучше). По сути, можно писать программы на C++, которые не ведут себя предсказуемо, даже если многие компиляторы C++ не будут сообщать о каких-либо ошибках в программе!

Давайте посмотрим на классический пример:

#include <iostream>

int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

Переменная p указывает на строковый литерал "hello!\n" и два нижеприведенных назначения пытаются изменить этот строковый литерал. Что делает эта программа? Согласно пункту 11 раздела 2.14.5 стандарта C++, он вызывает неопределенное поведение:

Эффект попытки изменить строковый литерал не определен.

Я слышу, как люди кричат: "Но подождите, я могу без проблем скомпилировать и получить результат". yellow "или" Что вы подразумеваете под неопределенным, строковые литералы хранятся в памяти только для чтения, поэтому первая попытка присваивания приводит к дампу ядра ". Это как раз проблема с неопределенным поведением. По сути, стандарт допускает все, что происходит после того, как вы вызывать неопределенное поведение (даже носовые демоны).Если существует "правильное" поведение в соответствии с вашей ментальной моделью языка, эта модель просто неверна: стандарт C++ имеет единственный голос - точка.

Другие примеры неопределенного поведения включают доступ к массиву за его пределами, разыменование нулевого указателя, доступ к объектам после истечения срока их жизни или запись предположительно умных выражений, таких как i++ + ++i,

В разделе 1.9 стандарта C++ также упоминаются два менее опасных брата неопределенного поведения: неопределенное поведение и поведение, определяемое реализацией:

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

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

Некоторые другие аспекты и операции абстрактной машины описаны в этом международном стандарте как неопределенные (например, порядок оценки аргументов функции). Там, где это возможно, этот международный стандарт определяет набор допустимого поведения. Они определяют недетерминированные аспекты абстрактной машины.

Некоторые другие операции описаны в этом международном стандарте как неопределенные (например, эффект разыменования нулевого указателя). [ Примечание: этот международный стандарт не устанавливает требований к поведению программ, которые содержат неопределенное поведение. - конец примечания ]

В частности, в разделе 1.3.24 говорится:

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

Что вы можете сделать, чтобы не столкнуться с неопределенным поведением? По сути, вы должны читать хорошие книги по С ++ от авторов, которые знают, о чем они говорят. Винт интернет-учебники. Винт Буллшильдт.

Ну, это в основном прямая копия-вставка из стандартного

3.4.1 1 поведение, определяемое реализацией, неопределенное поведение, где каждая реализация документирует, как сделан выбор

Пример 2 Примером поведения, определяемого реализацией, является распространение старшего бита, когда целое число со знаком сдвигается вправо.

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

2 ПРИМЕЧАНИЕ Возможное неопределенное поведение варьируется от полного игнорирования ситуации с непредсказуемыми результатами до поведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей или без выдачи диагностического сообщения), до прекращения перевода или выполнения (с выдача диагностического сообщения).

3 ПРИМЕР Примером неопределенного поведения является поведение при целочисленном переполнении.

3.4.4 1 неуказанное поведение использование неуказанного значения или другое поведение, когда этот международный стандарт предоставляет две или более возможности и не предъявляет никаких дополнительных требований к тому, что выбрано в любом случае

2 ПРИМЕР Примером неуказанного поведения является порядок, в котором оцениваются аргументы функции.

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

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

неопределенное поведение
Вы делаете что-то не так. Например, у вас есть очень большое значение в int это не вписывается в char, Как вы вкладываете это значение в char? на самом деле нет пути! Может произойти все что угодно, но самым разумным будет взять первый байт этого int и поместить его в char, Это просто неправильно делать это, чтобы назначить первый байт, но это то, что происходит под капотом.

неуточненное поведение
Какая функция из этих двух выполняется первой?

void fun(int n, int m);

int fun1()
{
  cout << "fun1";
  return 1;
}
int fun2()
{
  cout << "fun2";
  return 2;
}
...
fun(fun1(), fun2()); // which one is executed first?

Язык не определяет оценку, слева направо или справа налево! Таким образом, неопределенное поведение может привести или не привести к неопределенному поведению, но, безусловно, ваша программа не должна вызывать неопределенное поведение.


@eSKay Я думаю, что ваш вопрос стоит отредактировать ответ, чтобы уточнить больше:)

за fun(fun1(), fun2()); разве поведение "реализация не определена"? Компилятор должен выбрать один или другой курс, в конце концов?

Разница между реализацией, определенной и неуказанной, заключается в том, что компилятор должен выбирать поведение в первом случае, но это не обязательно во втором случае. Например, реализация должна иметь одно и только одно определение sizeof(int), Так что нельзя сказать, что sizeof(int) 4 для какой-то части программы и 8 для других. В отличие от неуказанного поведения, когда компилятор может сказать "ОК", я собираюсь оценить эти аргументы слева направо, а аргументы следующей функции - справа налево. Это может происходить в одной и той же программе, поэтому она называется неопределенной. На самом деле, C++ можно было бы сделать проще, если бы были указаны некоторые неуказанные поведения. Посмотрите здесь на ответ доктора Страуструпа для этого:

Утверждается, что разница между тем, что может быть создано, предоставляя компилятору эту свободу, и требуя "обычной оценки слева направо", может быть значительной. Я не уверен, но с бесчисленными компиляторами, "пользующимися свободой", и некоторыми людьми, страстно защищающими эту свободу, изменение будет трудным и может занять десятилетия, чтобы проникнуть в далекие уголки миров C и C++. Я разочарован тем, что не все компиляторы предостерегают от такого кода, как ++i+i++. Точно так же порядок оценки аргументов не определен.

В IMO слишком много "вещей", оставленных неопределенными, неуказанными, определяемыми реализацией и т. Д. Однако, это легко сказать и даже привести примеры, но трудно исправить. Следует также отметить, что не так уж и сложно избежать большинства проблем и создать переносимый код.

Из официального документа с обоснованием

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

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

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

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

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

Их окончательное резюме:

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

Реализация определена

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

Неопределенные -

То же, что определяется реализацией, но не задокументировано

Undefined-

Все может случиться, позаботься об этом.

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

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

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

Например, учитывая следующий код:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

реализация с двумя дополнительными компонентами не потребует каких-либо усилий для обработки выражения v << pow как сдвиг в два дополнения без учета того, v был положительным или отрицательным.

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

Стандарт C++ n3337 § 1.3.10Поведение, определяемое реализацией

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

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


Стандарт C++ n3337 § 1.3.24неопределенное поведение

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

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


Стандарт C++ n3337 § 1.3.25неопределенное поведение

поведение, для правильно сформированного программного построения и правильных данных, которое зависит от реализации [Примечание: реализация не требуется документировать, какое поведение происходит. Диапазон возможных поведений обычно определяется этим международным стандартом. - конец примечания]

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

Неопределенное поведение уродливо - например, «хорошее, плохое и уродливое».

Хорошо: программа, которая компилируется и работает по правильным причинам.

Плохо: программа, в которой есть ошибка, которую компилятор может обнаружить и на которую пожаловаться.

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

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

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

Например, код:

struct foo {int x;} = {0};
int main(void)
{
  foo.x = 1;
  return foo.x-1;
}

использует lvalue типа int [т.е. foo.x] для доступа к сохраненному значению объекта типа struct foo, хотя N1570 6.5p7 не содержит ничего, что позволило бы объект типа struct foo быть доступным, кроме как через lvalue типа struct foo или lvalue символьного типа, и при этом Стандарт не содержит языка, который освобождает выражения доступа к элементам структуры от требований 6.5p7.

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

Другие вопросы по тегам