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




undefined behavior c++ пример (6)

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


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

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

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 как оправдание, чтобы вычеркнуть окно ,


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

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

неопределенное поведение
Вы делаете что-то неправильно. Например, у вас очень большое значение в 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 слишком много «вещей» остаются неопределенными, неопределенными, определенными реализацией и т. Д. Однако это легко сказать и даже привести примеры, но трудно исправить. Следует также отметить, что не все так сложно избежать большинства проблем и создать переносимый код.


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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


Неопределенное поведение против Unspecified Behavior содержит краткое описание.

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

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





unspecified-behavior