ОБНОВЛЕНИЕ: Этот вопрос был темой моего блога 18 июня 2012 года . Спасибо за отличный вопрос!
Почему? Я хочу знать, было ли это дизайнерским решением, и если да, что это вызвало?
По сути, вы запрашиваете протокол заседания комитета по проектированию ANSI C, а у меня его нет. Если на ваш вопрос может ответить только кто-то, кто был в комнате в тот день, то вам нужно будет найти человека, который был в этой комнате.
Однако я могу ответить на более широкий вопрос:
Какие факторы побуждают комитет по разработке языков оставлять поведение легальной программы (*
) "неопределенным" или "определенным реализацией" (**
)?
Первый важный фактор: Существуют ли две существующие реализации языка на рынке, которые не согласны с поведением конкретной программы? Если компилятор FooCorp компилирует M(A(), B())
как "вызов A, вызовите B , вызовите M ", и компилятор BarCorp скомпилирует его как" вызовите B, вызовите A, вызовите M ", и ни одно из них не будет" явно правильным "поведением, тогда у комитета по языковому проектированию есть сильный стимул сказать" вы оба правы " и сделайте так, чтобы реализация определяла поведение. В частности, это тот случай, когда FooCorp и BarCorp имеют представителей в комитете.
Следующий важный фактор: предоставляет ли эта функция много разных возможностей для реализации? Например, в C # анализ компилятора выражения "понимание запроса" определен как "выполнить синтаксическое преобразование в эквивалентная программа, которая не имеет понимания запросов, а затем нормально анализирует эту программу ". У реализации очень мало свободы, чтобы делать иначе.
В отличие от этого, спецификация C # гласит, что цикл foreach
должен рассматриваться как эквивалентный цикл while
внутри блока try
, но обеспечивает реализацию некоторую гибкость. Компилятору C # разрешается говорить, например: «Я знаю, как реализовать семантику цикла foreach
более эффективно для массива» и использовать функцию индексации массива, а не преобразовывать массив в последовательность, как того требует спецификация.
Третий фактор: настолько сложна функция, что подробное описание ее точного поведения будет сложно или дорого определить? Спецификация C # действительно очень мало говорит о том, как анонимные методы, лямбда-выражения, должны быть реализованы деревья выражений, динамические вызовы, блоки итераторов и асинхронные блоки; он просто описывает желаемую семантику и некоторые ограничения на поведение и оставляет остальное до реализации.
Четвертый фактор: накладывает ли функция большую нагрузку на анализатор для компилятора? Например, в C #, если у вас есть:
Func<int, int> f1 = (int x)=>x + 1;
Func<int, int> f2 = (int x)=>x + 1;
bool b = object.ReferenceEquals(f1, f2);
Предположим, мы требуем, чтобы b было правдой. Как вы собираетесь определить, когда две функции "одинаковы" ? Проводя анализ «интенсиональности» - тела функций имеют одинаковое содержание? - сложно и выполняется анализ «экстенсиональности» - дают ли функции одинаковые результаты при одинаковых входных данных? - еще сложнее. Комитет по спецификации языка должен стремиться свести к минимуму количество открытых исследовательских задач, которые должна решить группа по внедрению!
В C # это поэтому остается определяемым реализацией; по своему усмотрению компилятор может сделать их ссылками равными или нет.
Пятый фактор: накладывает ли функция высокую нагрузку на среду выполнения?
Например, в C # разыменование за концом массива четко определено; это производит исключение индекса массива, вышедшего за пределы. Эта функция может быть реализована с небольшой - не нулевой, а небольшой - стоимостью во время выполнения. Вызов экземпляра или виртуального метода с нулевым получателем определяется как создание исключения null-was-dereferenced; опять же, это может быть реализовано с небольшой, но ненулевой стоимостью. Преимущество устранения неопределенного поведения окупается небольшими затратами времени выполнения.
Шестой фактор: исключает ли определенное поведение некоторую серьезную оптимизацию ? Например, C # определяет порядок побочных эффектов при наблюдении из потока, который вызывает побочные эффекты . Но поведение программы, которая наблюдает побочные эффекты одного потока от другого потока, определяется реализацией, за исключением нескольких «специальных» побочных эффектов. (Подобно изменчивой записи или вводу блокировки.) Если язык C # требует, чтобы все потоки наблюдали одинаковые побочные эффекты в одном и том же порядке, то мы должны были бы ограничить современные процессоры от эффективного выполнения своей работы; современные процессоры зависят от исполнения вне очереди и сложных стратегий кэширования для достижения высокого уровня производительности.
Это всего лишь несколько факторов, которые приходят на ум; Есть, конечно, много, много других факторов, которые комитеты языкового дизайна обсуждают, прежде чем сделать функцию «определенной реализацией» или «неопределенной».
Теперь вернемся к вашему конкретному примеру.
Язык C # делает строго определенным для этого поведения (†
); наблюдается побочный эффект приращения до появления побочного эффекта от назначения. Поэтому здесь не может быть никакого аргумента «ну, это просто невозможно», потому что можно выбрать поведение и придерживаться его. Это также не исключает больших возможностей для оптимизации. И не существует множества возможных сложных стратегий реализации.
Мой думаю , поэтому я подчеркиваю, что это догадка в том, что комитет по языку C упорядочил побочные эффекты в поведении, определяемом реализацией, потому что было несколько компиляторов Рынок, который делал это по-другому, явно не был «более правильным», и комитет не хотел сообщать половине из них, что они ошибались.
(*
) Или, иногда, его компилятор! Но давайте проигнорируем этот фактор.
(**
) Поведение «Undefined» означает, что код может делать что угодно , включая стирание вашего жесткого диска. Компилятор не обязан генерировать код, который имеет какое-либо конкретное поведение, и не обязан сообщать вам, что он генерирует код с неопределенным поведением. Поведение «определено реализацией» означает, что автору компилятора предоставляется значительная свобода в выборе стратегии реализации, но требуется выбрать стратегию , использовать ее последовательно и документ, который выбор .
(†
) Конечно, при наблюдении из одной нити.