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

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

9 ответов


неопределенное поведение - один из тех аспектов языка 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)). Таковы параметры абстрактной машины. Каждая реализация должна включать документацию, описывающую ее характеристики и поведение в этих отношениях.

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

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

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

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

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


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

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 для других. В отличие от неопределенного поведения, где компилятор может сказать OK, Я собираюсь оценить эти аргументы слева направо, а аргументы следующей функции оцениваются справа налево. Это может произойти в том же программа, вот почему она называется нет данных. Фактически, C++ можно было бы упростить, если бы были указаны некоторые из неопределенных поведений. Посмотрите здесь ответ доктора Страуструпа на это:

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

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


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

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

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

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

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


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

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

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


исторически сложилось так, что как поведение, определяемое реализацией, так и неопределенное поведение представляли ситуации, в которых авторы стандарта ожидали, что люди, пишущие качественные реализации, будут использовать суждение, чтобы решить, какие поведенческие гарантии, если таковые имеются, будут полезны для программ в предполагаемой области приложений, работающих на намеченных целях. Потребности высокопроизводительного кода хруста чисел сильно отличаются от потребностей кода низкоуровневых систем, и как 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++ не навязывает определенного поведения некоторым конструкциям, а говорит вместо этого, что необходимо выбрать конкретное, четко определенное поведение и описал по частности реализация (версия библиотеки). Так что пользователь может точно знать, как будет вести себя программа даже не описать это.


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

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

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


стандарт C++ n3337 § 1.3.25 неуказанному поведению

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

стандарт 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.

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