Unspecified, undefined и поведение, определяемое реализацией WIKI для C

Хотя на SO есть множество ссылок на эту тему, я думаю, что чего-то не хватает: ясное объяснение на простом языке о том, какие различия между неуказанным поведением (UsB), undefined поведение (UB) и поведение, определяемое реализацией (IDB), с подробным, но легким объяснением любого примера использования и примера.

Примечание: Я сделал сокращенное сокращение UsB ради компактности в этом WIKI, но не ожидаю, что он будет использоваться в другом месте.

Я знаю, что это может показаться дубликатом других сообщений (ближе к ним this), но , прежде чем кто-либо отметит это как дубликат, пожалуйста, рассмотрите, какие проблемы со всем материалом, который я уже нашел (и я собираюсь сделать сообщество WIKI из этого сообщения):

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

  • Примерами часто являются только коды с небольшими пояснениями. По таким деликатным вопросам, особенно для (относительных) новичков, может быть лучше подход сверху вниз: сначала ясное, простое объяснение с абстрактным (но не законным) описанием, затем несколько простых примеров с объяснениями, почему они вызывают некоторое поведение.

  • В некоторых сообщениях часто используется сочетание примеров на C и С++. C и С++ иногда не согласуются с тем, что они считают UsB, UB и IDB, поэтому пример может вводить в заблуждение для кого-то, кто не владеет знаниями на обоих языках.

  • Когда задается определение UsB, UB и IDB, обычно это простая ссылка на стандарты, которые иногда могут быть нечеткими или слишком сложными для переваривания для новичков.

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

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

Чтобы не испортить мою цель создания структурированного новичка WIKI, я бы хотел, чтобы плакаты следовали нескольким простым рекомендациям при редактировании WIKI:

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

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

  • Приведите стандарты по ссылке. Не публикуйте фрагменты различных стандартов, но дайте четкие ссылки (например, C99 WG14/N... раздел 1.4.7, абзац... ) и, если возможно, отправьте ссылку на соответствующий ресурс.

  • Предпочитайте бесплатные онлайн-ресурсы. Если вы хотите ссылаться на книги или не свободно доступные ресурсы, которые хорошо (и могут улучшить качество WIKI), но попытайтесь добавить также некоторые ссылки на свободные ресурсы. Это действительно важно, особенно для стандартов ИСО. Вы можете добавить ссылки на официальные стандарты, но попытайтесь добавить эквивалентную ссылку на свободно доступные черновики. И, пожалуйста, не заменяйте ссылки на черновики со ссылками на официальные стандарты, добавлять к ним. Даже некоторые отделы компьютерных наук в некоторых университетах не имеют копии стандартов (стандартов) ИСО, не говоря уже о большинстве программистов в целом!

  • Не отправляйте код, если это действительно необходимо. Почтовый код, только если объяснение с использованием только простого английского языка будет неудобным или непонятным. Попытайтесь ограничить образцы кода однострочными. Вместо этого отправляйте ссылки на другие SO Q & A.

  • Не публикуйте примеры на С++. Я бы хотел, чтобы это стало своего рода FAQ для C(Если кто-то хочет начать двухпоточность для С++, это было бы здорово, хотя). Соответствующие различия с С++ приветствуются, но только в качестве примечаний сторонних разработчиков. То есть после того, как вы подробно объясните C-пример, вы можете добавить пару утверждений о С++, если это поможет программисту C при переключении на С++, но я бы не хотел видеть примеры более чем, скажем, 20% -ного материала С++. Обычно простая заметка типа "(С++ ведет себя по-другому в этом случае)" плюс соответствующая ссылка должна быть достаточной.

Так как я довольно новичок в SO, я надеюсь, что не нарушу никакого правила, начав Q & A таким образом. Извините, если это так. Моды могут сообщить мне об этом.

Ответ 1

Стандарты

C определяют UsB, UB и IDB таким образом, который можно суммировать следующим образом:

Неопределенное поведение (UsB)

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

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

Подводя итог: стандарт дает некоторые возможности выбора, реализация выбирает, когда и как выбирается и применяется конкретная альтернатива.

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

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

Undefined Поведение (UB)

Как указано в названии, это ситуация, когда стандарт C не налагает или не гарантирует, что программа должна или должна делать. Все ставки сделаны. Такая ситуация:

  • делает программу ошибочной или непереносимой

  • не требует абсолютно ничего из реализации

Это очень неприятная ситуация: до тех пор, пока существует кусок кода с поведением undefined, вся программа считается ошибочной, а реализация разрешена стандартом делать все.

Другими словами, наличие причины UB позволяет реализации полностью игнорировать стандарт, если речь идет о запуске программы UB.

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

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

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

Примечание по терминологии:. Иногда люди утверждают, что часть кода, которую стандарт считает источником UB в своей реализации/системе/среде, документирована, поэтому это не может быть действительно UB. Это рассуждение неверно, но это общее (и несколько понятное) недоразумение: когда термин UB (а также UsB и IDB) используется в контексте C, это означает технический термин, чей < сильное > точное значение определяется стандартом (стандартами). В частности, слово "undefined" теряет свой повседневный смысл. Поэтому нет смысла показывать примеры, когда ошибочные или непереносимые программы производят "четко определенное" поведение в качестве контрпримеров. Если вы попробуете, вы действительно упустите момент. UB означает, что вы теряете все гарантии стандарта. Если ваша реализация предусматривает расширение, ваши гарантии будут только теми, которые вы выполняете. Если вы используете это расширение, ваша программа больше не является совместимой программой C (в некотором смысле, это не более программа на C, поскольку она больше не соответствует стандарту!).

Полезность поведения undefined

Общий вопрос об UB - это что-то в этих строках: "Если UB настолько противный, почему не стандартный мандат на реализацию ошибки при столкновении с UB?"

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

Во-вторых, существование UB в стандартах позволяет согласованной реализации предоставлять расширения на C, не считаясь несоответствующими в целом.

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

Выполняемое при реализации поведение (IDB)

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

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

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



Примеры неуказанного поведения

Порядок оценки

Аргументы функции

Порядок оценки аргументов функции не указан EXP30-C.

Например, в c(a(), b()); не указано, вызывается ли функция a до или после b. Единственная гарантия заключается в том, что оба вызываются до функции c.



Примеры поведения undefined

Указатели

Выделение нулевого указателя

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

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

Выделение неинициализированного указателя

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

Выделение недействительных указателей

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

Это самый злой вариант разыменования указателей UB: нет защитной сетки, предупреждения о компиляторе нет, есть только тот факт, что код может что-то сделать. И обычно это происходит: Большинство вредоносных атак используют этот вид поведения UB в программах, чтобы заставить программы вести себя так, как они хотят, чтобы они вели себя (например, установка трояна, кейлоггера, шифрование вашего жесткого диска и т.д.). Возможность форматированного жесткого диска становится очень реальной с таким типом UB!

Устранение констелляции

Если мы объявим объект как const, мы даем обещание компилятору, что мы никогда не изменим значение этого объекта. Во многих контекстах компиляторы обнаруживают такую ​​недействительную модификацию и кричат ​​на нас. Но если мы отбросим константу, как в этом фрагменте:

int const a = 42;
...
int* ap0 = &a;      //< error, compiler will tell us
int* ap1 = (int*)a; //< silences the compiler
...
*ap1 = 43;          //< UB ==> program crash?

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

категория 2

введите здесь название!

разместите свое объяснение здесь!



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

категория 1

введите здесь название!

разместите свое объяснение здесь!

Ответ 2

N1570 - это проект стандарта ISO C, очень близкий к официальному документу ISO.

N1256 - это более ранний черновик, включающий стандарт C99 плюс изменения из трех технических исправлений.

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

  • J.1 Неопределенное поведение
  • J.2 Undefined поведение
  • J.3 Поведение, определяемое реализацией
  • J.4 Локальное поведение
  • J.5 Общие расширения