Приращение указателей, точная последовательность

Я только начал изучать C, и я понял, что

*a = *b;
a++;
b++;

и

*a++ = *b++

эквивалентны, но это то, что на самом деле происходит, когда строка

*a++ = *b++

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

Ответ 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 разрешено делать это путь С#, но не требуется.

Ответ 2

1)

*a = *b;
a++;
b++;

эквивалентно

*a = *b;
a = a+1;
b = b+1

2)

x = *a++

эквивалентно

x = *a;
a = a+1;

и

*b++ = x

эквивалентно

*b = x;
b = b+1;

так

*a++ = *b++

эквивалентно

*a = *b;
a = a+1;
b = b+1

3)

*(++a) = *(++b)

эквивалентно

a = a+1;
b = b+1
*a = *b;

Ответ 3

Точная последовательность, в которой оцениваются выражения и применяемые побочные эффекты, остается неуказанной; все, что гарантировано, заключается в том, что результат *b++ (значение, которое в настоящее время указывает b) присваивается результату *a++ (значение, которое в настоящее время указывает a), и что оба указателя продвинуты, Точный порядок операций будет отличаться.

Если вы хотите знать, как обрабатывается ваша платформа, вы можете посмотреть сгенерированный машинный код, но имейте в виду, что он все равно может меняться в зависимости от настроек компилятора или окружающего кода.