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

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

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

а также

*a++ = *b++

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

*a++ = *b++

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

 Grijesh Chauhan29 июл. 2013 г., 16:40
В*a++ = *b++; , ++ так это постфикс*b назначен на*a тогдаb++ а такжеa++ выполнено.
 Paul R29 июл. 2013 г., 16:38
Компилятор может делать все, что ему нравится, при условии, что программа ведет себя правильно, поэтому никто не может сказать, как это будет в общем случае переводиться. Все, что вы можете сделать, это скомпилировать его любым используемым компилятором и посмотреть на сгенерированный код - это просто скажет вам, что ваш конкретный компилятор делает в данном конкретном случае.
 Eric Lippert30 июл. 2013 г., 00:19
Вы делаете общую ошибку новичка путаницыстаршинство с участиемпорядок побочных эффектов, На самом деле они имеют очень мало общего друг с другом. Когда ты сказалA() + B() * C() в C нет требованияB() а такжеC() быть вызванным раньшеA() просто так* имеет более высокий приоритет, чем+, Функции могут быть вызваны влюбой порядокДо тех пор, покаB() а такжеC() называются раньше*, A() называется раньше+, а также* называется раньше+, Компилятор может выбратьЛюбые порядок вызовов, который удовлетворяет этим ограничениям.
 Eric Lippert30 июл. 2013 г., 00:27
@GrijeshChauhan: Вы делаете распространенную ошибку новичка, предполагая, что операция постфикса имеет определенную точку, в которой происходят приращения. Это не. Соответствующий компилятор может делать приращения в любое время. Если вы не понимаете, почему это так, внимательно прочитайте мой ответ.
 Grijesh Chauhan29 июл. 2013 г., 16:39
Попытайтесь наблюдать и понять ассемблерный код, сгенерированныйgcc -S.

Ответы на вопрос(3)

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;
 Eric Lippert29 июл. 2013 г., 23:43
Это правда, чтоx=*a++ являетсятребуется быть эквивалентнымx=*a;a=a+1;? Может ли соответствующий компилятор реализовать его какtemp=a; a=a+1; x=*temp; ?

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

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

Решение Вопроса

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

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

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

*a++ = *b++;

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

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

VAR:*a должен быть оценен, чтобы произвести переменную, вызвать ееvarVAL:*b необходимо оценить, чтобы получить значение, назовите егоvalASSIGN: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 # спецификация языка требует, чтобы возникали побочные эффекты левой стороны присваиваниядо побочные эффекты правой стороны задания, и то, и другое происходит до побочного эффекта от назначения. Тот же код в 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 #, но нетребуется к.

 Grijesh Chauhan30 июл. 2013 г., 15:38
я прав здесь
 Destructor30 янв. 2016 г., 07:42
@haccks: тебе того же. Я также полагал, что, несмотря на присвоение имен, это не означает, что предварительное увеличение будет сначала записывать в память, а затем возвращаться, и это не означает, что после увеличения сначала будет возвращаться, а затем запись в память. Но это заблуждение было устранено сейчас.
 haccks31 июл. 2013 г., 23:49
Никогда не знал, прежде чем читать этот ответ, что выражениеa = *P++ можно оценить какvar = p; p = p + 1; a = *var!
 Eric Lippert30 июл. 2013 г., 16:03
@GrijeshChauhan: правильно; Любой порядок допустим в C. В C # спецификация требует, чтобы сначала производились побочные эффекты левой стороны, если таковые имеются. Тогда побочный эффект приростаp случается, то побочный эффект разыменования старого значенияp происходит (помните, что разыменование создает побочный эффект в C #, если указатель недействителен), и затем происходит побочный эффект присваивания. То есть в C # побочные эффекты случаютсяслева направо для подвыражений ив порядке приоритета для операторов.
 Grijesh Chauhan30 июл. 2013 г., 09:02
Итак, выражениеa = *p++; выполняется 2 способами(1) первыйp assigned in some VAR then because++ `имеет более высокий приоритет, поэтомуp обновляется, чтобы указать на следующее местоположение, а затем ввторой шаг старая ценностьp = VAR назначенa какa = *VAR. (2) первый*p назначить наa потому что++ тогда постфиксный операторp обновления, чтобы указать следующее местоположение.

Ваш ответ на вопрос