В Undefined Поведение

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

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

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

int a = INT_MAX + 1;

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

По моему мнению, UB иногда просто стандартный способ сказать мне: "Надеюсь, вы знаете, что делаете, потому что мы не можем гарантировать никаких гарантий того, что произойдет".

Следовательно, мой вопрос: безопасно ли иногда полагаться на зависящее от машины поведение, даже если стандарт C считает, что он вызывает UB, или "UB" действительно следует избегать, независимо от обстоятельств?

Ответ 1

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

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


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

Подписанное целочисленное переполнение:

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

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

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

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

Ответ 2

Нет.

Компилятор использует поведение undefined при оптимизации кода. Известным примером является строгая переполняющая семантика в компиляторе GCC (поиск strict-overflow здесь) Например, этот цикл

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

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

for (;;)
  ...

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

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

Ответ 3

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

И поведение undefined настолько ЛЕГКО, чтобы этого избежать - не пишите такой код! Так почему же такие люди, как вы, хотите возиться с ним?

Ответ 7

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