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

470

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

Показать ещё 4 комментария
Теги:
undefined-behavior
unspecified-behavior
c++-faq

9 ответов

318
Лучший ответ

Undefined поведение - один из тех аспектов языка 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", а два присваивания ниже пытаются изменить этот строковый литерал. Что делает эта программа? Согласно пункту 2.14.5 параграфа 11 стандарта С++ он вызывает поведение undefined:

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

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

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

В разделе 1.9 стандарта С++ упоминается поведение undefined двух менее опасных братьев, неопределенного поведения и поведение, определяемое реализацией:

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

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

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

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

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

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

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

  • 5
    Это странный факт, возникший в результате слияния, что этот ответ охватывает только C ++, но теги этого вопроса включают в себя C. C имеет другое понятие «неопределенное поведение»: ему все равно потребуется реализация для выдачи диагностических сообщений, даже если поведение также указано для быть неопределенным для определенных нарушений правил (нарушений ограничений).
  • 0
    @ Йоханнес: Это действительно плохо. Почему бы не дать ссылку на несколько ответов на вопрос?
Показать ещё 12 комментариев
83

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

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

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

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

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

3 ПРИМЕР Пример undefined поведение - это поведение на целочисленное переполнение.

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

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

  • 2
    В чем разница между реализацией, определяемой реализацией, и неуказанным поведением?
  • 22
    @Zolomon: Точно так же, как он говорит: в основном то же самое, за исключением того, что в случае реализации определяется реализация для документирования (чтобы гарантировать), что именно произойдет, в то время как в случае неуказанного реализация не требуется для документирования или что-нибудь гарантировать.
Показать ещё 13 комментариев
54

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

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

undefined поведение
Вы делаете что-то неправильно. Например, у вас очень большое значение в 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?

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


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

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

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

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

ИМО слишком много" вещей" осталось undefined, неопределенный, реализации и т.д. Однако, это легко сказать и даже дать примеры, но трудно исправить. Это следует также отметить, что это не все, что трудно избежать большинства проблемы и производство переносных код.

  • 0
    для fun(fun1(), fun2()); разве поведение "implementation defined" ? Компилятор должен выбрать один или другой курс, в конце концов?
  • 0
    @AraK: спасибо за объяснение. Теперь я это понимаю. Кстати, "I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left" я понимаю, что это can произойти. Действительно ли это с компиляторами, которые мы используем в эти дни?
Показать ещё 7 комментариев
22

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

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

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

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

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

  • 0
    Сверхсовременные авторы компиляторов также рассматривают «неопределенное поведение» как предоставление лицензии авторам компиляторов допущения, что программы никогда не получат входные данные, которые могут привести к неопределенному поведению, и произвольного изменения всех аспектов поведения программ при получении таких входных данных.
  • 0
    Еще один момент, который я только что заметил: C89 не использовал термин «расширение» для описания функций, которые были гарантированы в некоторых реализациях, но не в других. Авторы C89 признали, что большинство текущих реализаций будут одинаково обрабатывать арифметику со знаком и арифметику без знака, за исключением случаев, когда результаты используются определенным образом, и такая обработка применяется даже в случае переполнения со знаком; однако они не перечислили это как общее расширение в Приложении J2, что наводит меня на мысль, что они рассматривают это как естественное положение дел, а не как продолжение.
8

Undefined Поведение против неуказанного поведения содержит краткое описание.

Их итоговое резюме:

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

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

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

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

К сожалению, с середины 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 может быть отрицательным, если программа будет участвовать в Undefined Поведении, нет никаких причин, чтобы программный клип был отрицательным диапазоном of v. Несмотря на то, что левое смещение отрицательных значений использовалось для каждого отдельного компилятора значимости, и большая часть существующего кода опирается на это поведение, современная философия будет интерпретировать тот факт, что в Стандарте говорится, что отрицательные значения сдвига слева являются UB как подразумевая, что составители компилятора должны быть свободны игнорировать это.

  • 0
    Но обработка неопределенного поведения хорошим способом не бесплатна. Причиной того, что современные компиляторы демонстрируют такое странное поведение в некоторых случаях UB, является то, что они неустанно оптимизируют, и для того, чтобы сделать лучшую работу в этом, они должны быть в состоянии предположить, что UB никогда не возникает.
  • 0
    Но тот факт, что << - UB на отрицательных числах, является неприятной маленькой ловушкой, и я рад, что мне об этом напомнили!
Показать ещё 2 комментария
5

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

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

Unspecified -

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

Undefined -

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

  • 1
    Я думаю, что важно отметить, что практическое значение «неопределенного» изменилось за последние несколько лет. Раньше было то, что дано uint32_t s; можно ожидать, что вычисление 1u<<s когда s равно 33, может дать 0 или 2, но не сделает ничего другого. Однако более новые компиляторы, оценивающие 1u<<s могут заставить компилятор определить, что, поскольку s должно быть меньше 32 до этого, любой код до или после этого выражения, который будет релевантным, только если s был 32 или больше, может быть опущен.
4

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

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

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


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

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

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


Стандарт С++ n3337 § 1.3.25 неуказанное поведение

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

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

0

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

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

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

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню