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