На неопределенное поведение - PullRequest
11 голосов
/ 24 мая 2011

Обычно UB считают чем-то, чего следует избегать, и сам по себе текущий стандарт C перечисляет немало примеров в приложении J.

Однако есть случаи, когда я не вижу никакого вреда в использовании UB, кроме как жертвой переносимости.

Рассмотрим следующее определение:

int a = INT_MAX + 1;

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

На мой взгляд, UB иногда просто говорит мне о стандарте C: «Я надеюсь, что вы знаете, что делаете, потому что мы не можем дать никаких гарантий относительно того, что произойдет».

Отсюда мой вопрос: безопасно ли иногда полагаться на машинно-зависимое поведение, даже если стандарт C считает, что он вызывает UB, или действительно следует избегать «UB», независимо от обстоятельств?

Ответы [ 7 ]

15 голосов
/ 24 мая 2011

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

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


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

Переполнение со знаком целого числа:

Если арифметика для типа int (например) переполняется, результат не определен. Одним из примеров является то, что INT_MAX + 1 не гарантируется равным INT_MIN. Такое поведение включает определенные классы оптимизации, которые важны для некоторого кода.

Например, знание того, что INT_MAX + 1 не определено, позволяет оптимизировать X + 1 > X до true. Знание, что умножение «не может» переполниться (потому что это было бы неопределенным), позволяет оптимизировать X * 2 / 2 до X. Хотя это может показаться тривиальным, такого рода вещи обычно раскрываются при помощи встраивания и расширения макросов. Более важная оптимизация, которую это позволяет, - для <= циклов, таких как:

for (i = 0; i <= N; ++i) { ... }

В этом цикле компилятор может предположить, что цикл будет повторяться ровно N + 1 раз, если i не определено при переполнении, что позволяет задействовать широкий диапазон оптимизаций цикла. С другой стороны, если переменная определяется как обход по переполнению, тогда компилятор должен предположить, что цикл, возможно, бесконечен (что происходит, если N равен INT_MAX) - что затем отключает эти важные оптимизации цикла. Это особенно касается 64-битных платформ, так как очень много кода использует int в качестве индукционных переменных.

5 голосов
/ 24 мая 2011

номер

При оптимизации кода компилятор использует неопределенное поведение. Хорошо известным примером является семантика строгого переполнения в компиляторе GCC (поиск strict-overflow здесь ). Например, этот цикл

for (int i = 1; i != 0; ++i)
  ...

предположительно зависит от вашего "машинно-зависимого" поведения переполнения целочисленного типа со знаком. Однако компилятор GCC по правилам строгой семантики переполнения может (и будет) предполагать, что увеличение переменной int может только сделать ее больше и никогда не меньше. Это предположение заставит GCC оптимизировать арифметику и вместо этого генерировать бесконечный цикл

for (;;)
  ...

, поскольку это совершенно правильное проявление неопределенного поведения.

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

2 голосов
/ 30 марта 2012

Если стандарт C (или другого языка) заявляет, что некоторый конкретный код будет иметь неопределенное поведение в некоторой ситуации, это означает, что компилятор C может генерировать код, чтобы делать все, что он хочет в этой ситуации, , оставаясь при этом совместимым сэтот стандарт .Многие конкретные реализации языка имеют документированное поведение, которое выходит за рамки того, что требуется стандартным языковым стандартом.Например, Whizbang Compilers Inc. может явно указать, что его конкретная реализация memcpy всегда будет копировать отдельные байты в порядке адресов.В таком компиляторе код, подобный следующему:

  unsigned char z[256];
  z[0] = 0x53;
  z[1] = 0x4F;
  memcpy(z+2, z, 254);

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

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

2 голосов
/ 24 мая 2011

В общем, лучше этого полностью избегать. С другой стороны, если в документации вашего компилятора прямо указано, что для этого компилятора определено определенное значение UB для стандарта, вы можете использовать его, возможно, добавив некоторый механизм #ifdef / #error для блокировки компиляции в случае используется другой компилятор.

2 голосов
/ 24 мая 2011

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

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

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

0 голосов
/ 24 мая 2011

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

0 голосов
/ 24 мая 2011

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

А неопределенное поведение так легко избежать - не пишите такой код!Так почему такие люди, как вы, хотят с ним связываться?

...