Разница в & ldquo; ++ value & rdquo; в C и Java [dубликат]

Лично я просто использую текстовые файлы:

В среде рабочего стола откройте терминал, а
echo "whatever long text you have copied" > file
отпустите кнопку tty и
$(cat file)
713
задан 16 January 2018 в 20:50

9 ответов

Хорошее объяснение того, что происходит при таком вычислении, содержится в документе n1188 с сайта ISO W14.

Я объясняю идеи.

Главное правило из стандарт ISO 9899, ​​который применяется в этой ситуации, равен 6.5p2.

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

Точки последовательности в выражении типа i=i++ находятся перед i= и после i++.

В документе, который я привел выше, объясняется, что вы можете определить, что программа сформирована маленькими ящиками, каждая из которых содержит инструкции между двумя последовательными точками последовательности. Точки последовательности определены в приложении C стандарта, в случае i=i++ есть две точки последовательности, которые ограничивают полное выражение. Такое выражение синтаксически эквивалентно записи expression-statement в форме грамматики Бэксу-Наура (грамматика приведена в приложении A стандарта).

Таким образом, порядок инструкций внутри ящика не имеет четкого порядка.

i=i++

может быть интерпретирован как

tmp = i
i=i+1
i = tmp

или как

tmp = i
i = tmp
i=i+1

, поскольку обе эти формы интерпретируют код i=i++ действительны и потому, что оба генерируют разные ответы, поведение не определено.

Таким образом, точка последовательности может быть видна в начале и в конце каждого окна, которое составляет программу [поля являются атомными единицами в C] и внутри ящика порядок команд не определен во всех случаях.

EDIT:

Другим хорошим источником для объяснения таких неоднозначностей являются записи с сайта n1188 (также опубликованы как книга), а именно сайт ISO W14 и здесь и здесь.

5
ответ дан 15 August 2018 в 14:31

Хотя синтаксис выражений типа a = a++ или a++ + a++ является законным, поведение этих конструкций не определено, поскольку a в соответствии со стандартом C не соблюдается. C99 6.5p2:

Между предыдущей и следующей точкой последовательности объект должен иметь значение, которое его хранимое значение изменялось не более одного раза путем оценки выражения. [72] Кроме того, предыдущее значение должно быть считано только для определения сохраняемого значения [73]

С C99 6.5p2 далее уточняется, что

Между предыдущей и следующей точками последовательности объект должен иметь измененное значение хранимого значения не более одного раза путем оценки выражения. [72] Кроме того, предыдущее значение должно быть считано только для определения сохраняемого значения [73]

Различные пункты последовательности перечислены в Приложении C к C11 (и C99): [ ! d17] Между предыдущей и следующей точкой последовательности объект должен иметь неизмененное значение хранимого значения не более одного раза путем оценки выражения. [72] Кроме того, предыдущее значение должно быть считано только для определения значения, которое необходимо сохранить [73]

Формулировка того же C11 :

Между предыдущей и следующей точками последовательности объект должен иметь измененное значение не более чем один раз путем оценки выражения. [72] Кроме того, предыдущее значение должно быть считано только для определения сохраненного значения [73]

Вы можете обнаружить такие ошибки в программу, например, используя последнюю версию GCC с -Wall и -Werror, а затем GCC полностью откажется от компиляции вашей программы. Ниже приведен вывод gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

Важная часть состоит в том, чтобы знать, что точка последовательности - и что такое точка последовательности и что нет. Например, оператор запятой является точкой последовательности, поэтому

j = (i ++, ++ i);

четко определен и будет увеличивать i на один, выдать старое значение, отбросить это значение; затем в операторе запятой уложите побочные эффекты; а затем приращение i на единицу, и результирующее значение становится значением выражения - т. е. это всего лишь ухищренный способ написать j = (i += 2), который снова является «умным» способом записи

i += 2;
j = i;

Тем не менее, , в списках аргументов функции является comma operator оператором запятой и не существует точки последовательности между оценками различных аргументов; вместо этого они не зависят от друг друга; поэтому вызов функции

int i = 0;
printf("%d %d\n", i++, ++i, i);

имеет неопределенное поведение, потому что синтаксис , поэтому значение i поэтому изменяется дважды, как i++, так и ++i, между предыдущей и следующей точки последовательности.

10
ответ дан 15 August 2018 в 14:31

Просто скомпилируйте и разобщите свою строку кода, если вы так склонны знать, как именно вы получаете то, что получаете.

Это то, что я получаю на своей машине, вместе с тем, что я думаю, что происходит:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(I ... предположим, что инструкция 0x00000014 была какой-то оптимизацией компилятора?)

75
ответ дан 15 August 2018 в 14:31

Я думаю, что соответствующие части стандарта C99 составляют 6.5. Выражения, §2

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

и 6.5.16 Операторы присваивания, §4:

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

55
ответ дан 15 August 2018 в 14:31

В https://stackoverflow.com/questions/29505280/incrementing-array-index-in-c кто-то спросил об утверждении типа:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

, который печатает 7 ... OP ожидал, что он напечатает 6.

Приращения ++i не гарантируются, чтобы все было выполнено до остальных вычислений. Фактически, разные компиляторы получат разные результаты. В примере, который вы указали, были выполнены первые 2 ++i, затем были прочитаны значения k[], затем последний ++i, затем k[].

num = k[i+1]+k[i+2] + k[i+3];
i += 3

Современные компиляторы оптимизируют это очень хорошо. На самом деле, возможно, лучше, чем код, который вы изначально писали (предполагая, что он работал так, как вы надеялись).

42
ответ дан 15 August 2018 в 14:31

Причина в том, что в программе выполняется неопределенное поведение. Проблема заключается в порядке оценки, поскольку в соответствии со стандартом C ++ 98 нет точек последовательности (никакие операции не секвенированы до или после другого в соответствии с терминологией C ++ 11).

Однако, если вы придерживайтесь одного компилятора, вы найдете поведение постоянным, если не будете добавлять вызовы функций или указатели, что сделало бы поведение более беспорядочным.

Итак, сначала GCC: Использование Nuwen MinGW 15 GCC 7.1 вы получите:
#include<stdio.h>
int main(int argc, char ** argv)
{
int i = 0;
i = i++ + ++i;
printf("%d\n", i); // 2

i = 1;
i = (i++);
printf("%d\n", i); //1

volatile int u = 0;
u = u++ + ++u;
printf("%d\n", u); // 2

u = 1;
u = (u++);
printf("%d\n", u); //1

register int v = 0;
v = v++ + ++v;
printf("%d\n", v); //2
}

Как работает GCC? он оценивает подвыражения в порядке слева направо для правой стороны (RHS), затем присваивает значение левой стороне (LHS). Именно так ведут себя Java и C # и определяют их стандарты. (Да, эквивалентное программное обеспечение на Java и C # определило поведение). Он оценивает каждое вспомогательное выражение один за другим в Заявлении RHS в порядке слева направо; для каждого вспомогательного выражения: сначала выполняется оценка ++ c (pre-increment), затем значение c используется для операции, затем приращение post c ++).

в соответствии с GCC C ++: Операторы

В GCC C ++ приоритет операторов контролирует порядок, в котором отдельные операторы оцениваются

эквивалентный код в определенном поведении C ++, поскольку GCC понимает:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

Затем переходим к GCC C ++: Операторы . Visual Studio 2015 вы получаете:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

Как работает визуальная студия, требуется другой подход, он оценивает все выражения предварительного приращения в первом проходе, затем использует значения переменных в операциях во втором проходе, назначать из RHS на LHS в третьем проходе, затем, наконец, пропустить, он оценивает все выражения после инкремента за один проход.

Таким образом, эквивалент в определенном поведении C ++, как Visual C ++, понимает:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

, поскольку документация Visual Studio указывает на приоритет и порядок оценки:

В GCC C ++ приоритет операторов контролирует порядок, в котором оцениваются отдельные операторы

3
ответ дан 15 August 2018 в 14:31

В стандарте C указано, что переменная должна назначаться не более одного раза между двумя точками последовательности. Например, точка с запятой - это точка последовательности. Поэтому каждое утверждение вида:

i = i++;
i = i++ + ++i;

и т. Д. Нарушает это правило. В стандарте также говорится, что поведение не определено, а не неопределено. Некоторые компиляторы обнаруживают их и приводят к некоторому результату, но это не соответствует стандарту. [D1]

Однако две разные переменные могут увеличиваться между двумя точками последовательности.

while(*src++ = *dst++);

Вышеприведенная общая практика кодирования при копировании / анализе строк.

13
ответ дан 15 August 2018 в 14:31

Другой способ ответить на это, вместо того, чтобы увязнуть в тайных деталях точек последовательности и неопределенного поведения, - это просто спросить, что они должны означать? Что пытался сделать программист?

Первый фрагмент, о котором я спрашивал, i = i++ + ++i, в моей книге довольно сумасшедший. Никто никогда не напишет его в реальной программе, не очевидно, что он делает, нет никакого мыслимого алгоритма, который кто-то мог бы попытаться закодировать, что привело бы к этой конкретной надуманной последовательности операций. И поскольку для вас и меня не очевидно, что он должен делать, это прекрасно в моей книге, если компилятор не может понять, что он должен делать.

Второй фрагмент i = i++ , немного легче понять. Кто-то явно пытается увеличить i и присвоить результат i. Но есть несколько способов сделать это в C. Самый простой способ добавить 1 к i и присвоить результат обратно i, одинаково практически для любого языка программирования:

i = i + 1

C , конечно, имеет удобный ярлык:

i++

Это означает, что «добавьте 1 к i и присвойте результат обратно i». Итак, если мы построим мешанину из двух, написав

i = i++

, то, что мы действительно говорим, это «добавить 1 к i» и присвоить результат обратно i и присвоить результат обратно i ». Мы смущены, поэтому меня это слишком беспокоит, если компилятор тоже запутался.

Реально, единственный раз, когда эти сумасшедшие выражения записываются, когда люди используют их как искусственные примеры того, как ++ должен работать. И, конечно же, важно понимать, как работает ++. Но одно практическое правило для использования ++ - «Если не очевидно, что означает выражение, использующее ++, не пишите».

Мы проводили бесчисленные часы на comp.lang.c, обсуждая выражения, подобные этим и , что они должны означать? , они не определены. Два моих более длинных ответа, которые пытаются объяснить, почему, архивируются в Интернете:

Почему Стандарт не определяет, что они делают? Не соответствует ли приоритет оператора порядку оценки?
27
ответ дан 15 August 2018 в 14:31

Хотя маловероятно, что какие-либо компиляторы и процессоры действительно это сделают, было бы законно в соответствии со стандартом C для компилятора реализовать «i ++» с последовательностью:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

Пока Я не думаю, что какие-либо процессоры поддерживают аппаратное обеспечение, позволяющее эффективно выполнять такую ​​работу, легко представить себе ситуации, когда такое поведение упростит многопоточный код (например, это гарантирует, что если два потока попытаются выполнить описанную выше последовательность одновременно i будет увеличиваться на два), и совершенно немыслимо, чтобы какой-то будущий процессор мог предоставить что-то подобное.

Если компилятор должен был написать i++, как указано выше (в соответствии с стандарт) и должны были интерпретировать приведенные выше инструкции в ходе оценки общего выражения (также законного), и если бы не было уведомления о том, что одна из других инструкций имела доступ к i, это было бы возможно (и юридический) для компилятора для генерации последовательности instr которые могли бы затормозить. Разумеется, компилятор почти наверняка обнаружит проблему в том случае, если в обоих местах используется одна и ту же переменная i, но если подпрограмма принимает ссылки на два указателя p и q и использует (*p) и (*q) в приведенном выше выражении (вместо того, чтобы дважды использовать i), компилятору не потребовалось бы распознавать или избегать тупиковой ситуации, которая произошла бы, если бы тот же адрес объекта был передан как для p, так и для q. [ ! d2]

22
ответ дан 15 August 2018 в 14:31

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

Похожие вопросы: