Да, статья MSDN содержит ошибку, по крайней мере, в отношении стандартов C и C ++ 1 .
Сказав это, позвольте мне начать с примечания о терминологии: в стандарте C ++ они (в основном - есть несколько ошибок) используют «оценку» для обозначения вычисления операнда и «вычисление значения» ссылаться на проведение операции. Таким образом, когда (например) вы делаете a + b
, каждый из a
и b
оценивается, тогда выполняется вычисление значения для определения результата.
Понятно, что порядок вычислений значений (в основном) контролируется приоритетом и ассоциативностью - управление вычислениями значений - это, в основном, определение того, что приоритет и ассоциативность равны . Остальная часть этого ответа использует «оценку» для ссылки на оценку операндов, а не для вычисления вычислений.
Теперь, что касается порядка оценки, определяемого по приоритету, нет, это не так! Это так просто. Просто для примера, давайте рассмотрим ваш пример x<y<z
. Согласно правилам ассоциативности, это разбирается как (x<y)<z
. Теперь рассмотрим оценку этого выражения на стековой машине. Для него вполне допустимо сделать что-то вроде этого:
push(z); // Evaluates its argument and pushes value on stack
push(y);
push(x);
test_less(); // compares TOS to TOS(1), pushes result on stack
test_less();
Это оценивает z
до x
или y
, но все равно оценивает (x<y)
, а затем сравнивает результат этого сравнения с z
, как и положено.
Резюме: порядок оценки не зависит от ассоциативности.
Приоритет такой же. Мы можем изменить выражение на x*y+z
и по-прежнему оценивать z
до x
или y
:
push(z);
push(y);
push(x);
mul();
add();
Резюме: порядок оценки не зависит от приоритета.
Когда / если мы добавим побочные эффекты, это останется прежним. Я думаю, что образовательно думать о побочных эффектах, как о выполняемых отдельным потоком выполнения, с join
в следующей точке последовательности (например, конец выражения). Так что что-то вроде a=b++ + ++c;
может быть выполнено примерно так:
push(a);
push(b);
push(c+1);
side_effects_thread.queue(inc, b);
side_effects_thread.queue(inc, c);
add();
assign();
join(side_effects_thread);
Это также показывает, почему очевидная зависимость также не обязательно влияет на порядок оценки. Даже если a
является целью назначения, это все равно оценивает a
до , оценивая либо b
, либо c
. Также обратите внимание, что, хотя я написал это как «поток» выше, это также может быть пул потоков, все выполняющиеся параллельно, так что вы не получите никакой гарантии о порядке один шаг по сравнению с другим либо.
Если бы аппаратное обеспечение не имело прямой (и дешевой ) поддержки поточно-ориентированных очередей, это, вероятно, не использовалось бы в реальной реализации (и даже тогда это маловероятно). Помещение чего-либо в потокобезопасную очередь, как правило, требует гораздо больше накладных расходов, чем выполнение одного приращения, поэтому трудно представить, чтобы кто-нибудь когда-либо делал это в реальности. Концептуально, однако, идея соответствует требованиям стандарта: когда вы используете операцию до / после увеличения / уменьшения, вы указываете операцию, которая произойдет через некоторое время после оценки этой части выражения, и будет выполнена в следующая точка последовательности.
Редактировать: хотя это не совсем многопоточность, некоторые архитектуры допускают такое параллельное выполнение. Для пары примеров процессоры Intel Itanium и VLIW, такие как некоторые DSP, позволяют компилятору назначать ряд команд для параллельного выполнения. Большинство машин VLIW имеют определенный размер «пакета» команд, который ограничивает количество команд, выполняемых параллельно. Itanium также использует пакеты инструкций, но указывает бит в пакете инструкций, чтобы сказать, что инструкции в текущем пакете могут выполняться параллельно с инструкциями в следующем пакете. Используя подобные механизмы, вы получаете инструкции, выполняемые параллельно, как если бы вы использовали несколько потоков в архитектурах, с которыми большинство из нас более знакомы.
Резюме: порядок оценки не зависит от очевидных зависимостей
Любая попытка использовать значение до следующей точки последовательности приводит к неопределенному поведению - в частности, «другой поток» (потенциально) изменяет эти данные в течение этого времени, и у вас есть нет способсинхронизировать доступ с другим потоком.Любая попытка его использования приводит к неопределенному поведению.
Просто для примера (по общему признанию, теперь довольно надуманного), представьте, что ваш код работает на 64-битной виртуальной машине, но реальное оборудование - 8.процессорКогда вы увеличиваете 64-битную переменную, она выполняет последовательность, которая выглядит примерно так:
load variable[0]
increment
store variable[0]
for (int i=1; i<8; i++) {
load variable[i]
add_with_carry 0
store variable[i]
}
Если вы прочитаете значение где-то в середине этой последовательности, вы можете получить что-то с измененными только некоторыми байтами,так что вы получаете ни старое значение , ни новое.
Этот точный пример может быть довольно надуманным, но менее экстремальной версией (например, 64-битная переменная на32-разрядный компьютер) на самом деле довольно распространен.
Заключение
Порядок оценки не зависит от приоритета, ассоциативности или (обязательно) от очевидных зависимостей.Попытка использовать переменную, к которой был применен предварительный / последующий инкремент / декремент в любой другой части выражения, действительно дает полностью неопределенное поведение.Хотя реальный сбой маловероятен, вы определенно не гарантированно получите либо старое, либо новое значение - вы можете получить что-то еще полностью.
1 Я не проверял эту конкретную статью, но довольно много статей MSDN рассказывают о Microsoft Managed C ++ и / или C ++ / CLI (или специфичны для их реализации C ++), но мало или ничего не указывают на то, что онине относится к стандарту C или C ++Это может создать ложное представление о том, что они утверждают, что правила, которые они решили применить к своим собственным языкам, фактически применяются к стандартным языкам.В этих случаях статьи не являются технически ложными - они просто не имеют ничего общего со стандартом C или C ++.Если вы попытаетесь применить эти операторы к стандарту C или C ++, результат будет ложным.