Ответ 1
Ты сказал, что считаешь, что:
*a = *b; a++; b++;
эквивалентно
*a++ = *b++;
но это неверно, поэтому у вас ложное убеждение. Позвольте исправить ложную веру.
В первом случае должно произойти следующее:
- VAR:
*a
должен быть оценен для создания переменной, назовите ееvar
- VAL:
*b
должен быть оценен для получения значения, назовите егоval
- ASSIGN:
val
должен быть назначенvar
. - INCA:
a
должен быть увеличен. - INCB:
b
должен быть увеличен.
Каковы ограничения на то, как компилятор может их заказать?
- VAR и VAL должны выполняться до ASSIGN.
- ДОКУМЕНТ должен произойти до INCA.
- INCA должна произойти до INCB.
Правило здесь состоит в том, что все побочные эффекты одного оператора должны быть полными до начала следующего утверждения. Таким образом, существуют два правовых порядка. VAR VAL ASSIGN INCA INCB, или VAL VAR ASSIGN INCA INCB.
Теперь рассмотрим второй случай.
*a++ = *b++;
У нас есть те же пять операций, но ограничения на их упорядочение совершенно разные, потому что все они находятся в одном и том же выражении, поэтому правило о операторах не применяется. Теперь существуют следующие ограничения:
- VAR и VAL должны выполняться до ASSIGN.
- оценка VAR должна использовать исходное значение
a
- оценка VAL должна использовать исходное значение
b
Обратите внимание, что я не сказал, что требуется, чтобы приращения произошли впоследствии. Скорее, я сказал, что должны использоваться исходные значения. Пока используется исходное значение, инкремент может произойти в любое время.
Так, например, было бы совершенно законно генерировать это как
var = a;
a = a + 1; // increment a before assign
*var = *b;
b = b + 1; // increment b after assign
Было бы также законно:
val = *b;
b = b + 1; // increment b before assign
*a = val;
a = a + 1; // increment a after assign
Также было бы законно делать это, как вы предлагаете: сначала выполните назначение, а затем оба увеличения в порядке слева направо. И также было бы законно выполнять назначение сначала, а затем оба приращения в порядке справа налево.
Компилятор C предоставляет широкую широту для генерации кода, однако ему нравится этот вид выражения. Убедитесь, что это очень ясно в вашем сознании, потому что большинство людей ошибаются: только потому, что ++
появляется после того, как переменная не означает, что приращение происходит позже. Приращение может произойти уже компилятору нравится, пока компилятор гарантирует, что используется исходное значение.
Это правило для C и С++. В С# спецификация языка требует, чтобы побочные эффекты левой стороны присваивания происходили до побочных эффектов правой стороны задания, и что оба они происходят до побочного эффекта назначения. Тот же код в С# должен быть сгенерирован как:
var_a = a;
a = a + 1;
// must pointer check var_a here
var_b = b;
b = b + 1;
val = *var_b; // pointer checks var_b
*var_a = val;
"Проверка указателя" - это точка, в которой С# требует, чтобы среда выполнения проверяла, что var_a
является допустимым указателем; другими словами, что *var_a
на самом деле является переменной. Если это не так, он должен выдать исключение, прежде чем b
будет оценен.
Снова, компилятору C разрешено делать это путь С#, но не требуется.