2016-04-10 4 views
3

Я в последнее время пытаюсь научиться C. Исходя из Java, он удивил меня тем, что вы можете выполнять определенные операции, объявленные как «неопределенные».Почему неопределенное поведение разрешено в C

Это кажется мне крайне небезопасным. Я понимаю, что ответственность программиста заключается в том, чтобы не выполнять неопределенные операции, но почему это даже разрешено начинать? Почему компилятор не улавливает, например, индексы массива за пределами границ или даже оборванные указатели? Вы просто получаете доступ к блокам памяти, к которым вы никогда не должны обращаться, без какой-либо (очевидной) веской причины.

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

Несомненно, должна быть причина, по которой это разрешено? Что это?

ОТВЕТ: Насколько я понимаю, основной причиной является производительность. Кроме того, Java имеет неопределенное поведение, хотя и не помечено как таковое.

EDIT: ограниченный вопрос C

+0

Вы имеете в виду, кроме очевидной причины? –

+8

Существует штраф за выполнение при проверке границ каждого доступа к одному массиву. –

+0

, потому что это довольно трудно запретить, по крайней мере, в прошлом и сейчас. – user3528438

ответ

2

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

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

/* Assume USmall is half the size of "int" */ 
unsigned mult(USmall x, USmall y) { return x*y; } 

которая во всех случаях, в том числе тех, где математическое произведение х и у было между INT_MAX + 1 и UINT_MAX, эквивалентны (unsigned)x*y;. Я не вижу причин полагать, что они не ожидали продолжения этой тенденции.

К сожалению, новая философия стала модной, основанной на ревизионистской точке зрения, что авторы-компиляторы поддерживали только полезное поведение в случаях, не предусмотренных Стандартом, потому что они были слишком неискушенными, чтобы делать что-либо еще. Например, в gcc, используя уровень оптимизации 2, но без других параметров, отличных от параметров по умолчанию, вышеупомянутая процедура «mult» иногда генерирует фиктивный код в случаях, когда продукт будет находиться между 0x80000000u и 0xFFFFFFFFu, даже если он работает на платформах, где такие вычисления будут исторически работали. Предполагается, что это делается во имя «оптимизации»; было бы интересно узнать, сколько из «оптимизаций» таких методов в конечном итоге выполняют действительно полезны и не могли быть достигнуты с помощью более безопасных средств.

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

-2

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

Проверка времени выполнения на C++ при использовании метода at() для вектора. Это то, что отличает в() от оператора []

3

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

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

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

Аналогичным образом можно предотвратить чтение/запись за конец массива путем проверки его границ внутри оператора []. Однако это обойдется вам в несколько дополнительных циклов процессора для каждого доступа к массиву.

Дизайнеры C++ решили, что лучше иметь скорость и позволить потенциальному UB, чем заставить всех платить за то, что им не нужно. Этот подход, однако, несовместим с требованием Java «написать один раз, выполнить где угодно», поэтому разработчики Java-языка настаивали на полностью определенном поведении почти во всех ситуациях.

+1

Не полностью ли определено поведение во всех ситуациях, немного сильное? –

+0

@PaulBoddington. Помимо JNI, стандарт Java является «контрольным уродством»: либо компилятор создает ошибку, либо вы получаете поведение, описанное в стандарте. По крайней мере, это то, на что надеются стандартные писатели. – dasblinkenlight

+0

Это правда, и мне очень нравится Java из-за этого, но есть некоторое неопределенное поведение, хотя - например. вы не можете предсказать, когда размер «WeakHashMap» может измениться. –