Инкрементные указатели, точная последовательность

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

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

а также

*a++ = *b++

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

*a++ = *b++

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

3 ответа

Решение

Вы сказали, что считаете, что:

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

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

*a++ = *b++;

но это неверно, поэтому у вас ложное убеждение. Давайте исправим ваше ложное убеждение.

В первом случае должно произойти следующее:

  • VAR: *a должен быть оценен, чтобы произвести переменную, вызвать ее var
  • VAL: *b необходимо оценить, чтобы получить значение, назовите его val
  • ASSIGN: val должен быть назначен var,
  • INCA: a должен быть увеличен.
  • МККН: b должен быть увеличен.

Каковы ограничения на то, как компилятор может их упорядочить?

  • VAR и VAL должны произойти до назначения.
  • Назначение должно произойти до INCA.
  • INCA должно произойти до INCB.

Здесь правило состоит в том, что все побочные эффекты одного оператора должны быть завершены до начала следующего оператора. Таким образом, есть два юридических заказа. VAR VAL ASSIGN INCA INCB или VAL VAR ASSIGN INCA INCB.

Теперь давайте рассмотрим второй случай.

*a++ = *b++;

У нас одни и те же пять операций, но ограничения на их порядок совершенно разные, потому что все они в одном и том же утверждении, поэтому правило об утверждениях не применяется. Теперь ограничения:

  • VAR и VAL должны произойти до назначения.
  • оценка 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++. В 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;

"Проверка указателя" - это точка, в которой C# требует, чтобы среда выполнения проверила, что var_a является действительным указателем; другими словами, что *var_a на самом деле переменная. Если это не так, он должен выбросить исключение, прежде чем b оценивается.

Опять же, компилятору C разрешено делать это способом C#, но это не обязательно.

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;

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

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

Другие вопросы по тегам