Оператор Comma и приращение при печати в C [duplicate]

Если вы выполняете вызов несколько раз, вы можете использовать новые дескрипторы методов, введенные в Java 7. Здесь мы переходим к тому, чтобы ваш метод возвращал строку:

Object obj = new Point( 100, 200 );
String methodName = "toString";  
Class<String> resultType = String.class;

MethodType mt = MethodType.methodType( resultType );
MethodHandle methodHandle = MethodHandles.lookup().findVirtual( obj.getClass(), methodName, mt );
String result = resultType.cast( methodHandle.invoke( obj ) );

System.out.println( result );  // java.awt.Point[x=100,y=200]
714
задан Antti Haapala 16 January 2018 в 17:50
поделиться

13 ответов

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

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

Итак, имея в виду, почему эти «проблемы»? Язык ясно говорит, что определенные вещи приводят к неопределенному поведению . Нет проблем, нет «необходимости». Если неопределенное поведение изменяется, когда объявлена ​​одна из вовлеченных переменных volatile, это ничего не доказывает или не изменяет. Это undefined ; вы не можете рассуждать о поведении.

Ваш наиболее интересный пример, один с

u = (u++);

является примером неопределенного поведения в текстовой книге (см. запись Википедии в точек последовательности ).

514
ответ дан DaveRandom 16 August 2018 в 06:48
поделиться
  • 1
    Я знал, что это было неопределенно (идея о том, как использовать этот код в производстве, пугает меня :)), но я попытался понять, в чем причина этих результатов. Тем более, что u = u ++ увеличивает u. В java например: u = u ++ возвращает 0, так как (мой мозг) ожидался :) Спасибо за последовательность точек ссылок BTW. – PiX 4 June 2009 в 10:42
  • 2
    @PiX: Вещи не определены по ряду возможных причин. К ним относятся: нет четкого «правильного результата», разные архитектуры машин будут сильно способствовать разным результатам, существующая практика не является последовательной или выходит за рамки стандарта (например, какие имена файлов действительны). – Richard 4 June 2009 в 11:57
  • 3
    Дух C: Доверяйте программисту ... как бы он ни был сумасшедшим. – Fiddling Bits 26 November 2013 в 04:48
  • 4
    @rusty Не уверен, что вы имеете в виду. Термин "неопределенное поведение" используется в стандарте C. Это означает, что, хотя некоторые конструкции синтаксически действительны и обычно компилируются, они приводят к неопределенному поведению, то есть они не имеют смысла и их следует избегать, так как ваша программа нарушена, если она имеет неопределенное поведение. – unwind 22 March 2014 в 22:01
  • 5
    @MattMcNabb, который только определен в C ++ 11 не в C11. – Shafik Yaghmour 14 July 2014 в 02:18

Хорошее объяснение того, что происходит при таком вычислении, содержится в документе 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:

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

5
ответ дан alinsoar 16 August 2018 в 06:48
поделиться
  • 1
    Я редактировал вопрос, чтобы добавить UB в оценку аргументов функции, так как этот вопрос часто используется в качестве дубликата для этого. (Последний пример) – Antti Haapala 21 October 2017 в 10:46
  • 2
    @AnttiHaapala Другой вопрос, связанный с этим, - a[i]=i++ - which index of a is modified ? – alinsoar 22 October 2017 в 10:43
  • 3
    Я только что добавил это, спасибо. – Antti Haapala 22 October 2017 в 10:51
  • 4
    @AnttiHaapala спасибо, я также закончил еще несколько комментариев. – alinsoar 23 October 2017 в 06:40
  • 5
    Как этот ответ добавился к существующим ответам? Также объяснения для i=i++ очень похожи на этот ответ . – haccks 24 November 2017 в 08:00

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

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

С помощью сноски 73 далее уточняется, что

  1. Этот абзац отображает неопределенные выражения операторов, такие как
    i = ++i + 1;
    a[i++] = i;
    
    , позволяя
    i = i + 1;
    a[i] = i;
    

. В списке перечислены различные точки последовательности в приложении C к C11 C99 ):

  1. Ниже приведены точки последовательности, описанные в 5.1.2.3: Между оценки указателя функции и фактических аргументов в вызове функции и фактическом вызове. (6.5.2.2). Между оценками первого и второго операндов следующих операторов: логическое И & amp; & amp; (6.5.13); логический ИЛИ || (6.5.14); запятая, (6.5.17). Между оценками первого операнда условного? : оператор и в зависимости от второго и третьего операндов (6.5.15). Конец полного декларатора: деклараторы (6.7.6); Между оценкой полного выражения и следующим полным выражением, которое должно быть оценено. Ниже приведены полные выражения: инициализатор, не являющийся частью составного литерала (6.7.9); выражение в выражении выражения (6.8.3); управляющее выражение оператора выбора (if или switch) (6.8.4); управляющее выражение while или do (6.8.5); каждое из (необязательных) выражений оператора for (6.8.5.3); (необязательное) выражение в операторе return (6.8.6.4). Непосредственно перед возвратом функции библиотеки (7.1.4). После действий, связанных с каждым форматированным спецификатором преобразования функции ввода / вывода (7.21.6, 7.29.2). Непосредственно перед и сразу после каждого вызова функции сравнения, а также между любым вызовом функции сравнения и любым перемещением объектов, переданных в качестве аргументов для этого вызова (7.22.5).

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

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

Вы можете обнаружить такие ошибки в программе, например, используя последнюю версию 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;

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

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

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

10
ответ дан Antti Haapala 16 August 2018 в 06:48
поделиться

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

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

$ 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
ответ дан badp 16 August 2018 в 06:48
поделиться
  • 1
    как я могу получить машинный код? Я использую Dev C ++, и я играл с опцией «Генерация кода» в настройках компилятора, но не делал дополнительного вывода файла или какого-либо выхода на консоль – bad_keypoints 24 September 2012 в 15:11
  • 2
    @ronnieaka gcc evil.c -c -o evil.bin и gdb evil.bindisassemble evil, или любые эквиваленты Windows из них: – badp 24 September 2012 в 19:20
  • 3
    Этот ответ действительно не затрагивает вопрос о Why are these constructs undefined behavior?. – Shafik Yaghmour 1 July 2014 в 15:00
  • 4
    В стороне, легче будет скомпилировать сборку (с gcc -S evil.c), которая здесь нужна всем. Сборка, а затем разборка - это просто окольный способ сделать это. – Kat 27 July 2015 в 20:32
  • 5
    Для записи, если по какой-либо причине вам интересно, что делает данная конструкция - и особенно , если есть подозрения, что это может быть неопределенное поведение - вековой совет «просто попробуйте» это с вашим компилятором и увидеть & quot; потенциально опасна. В лучшем случае вы узнаете, что он делает в этой версии вашего компилятора в этих обстоятельствах сегодня . Вы not много узнаете, если что-нибудь о том, что это гарантировано. В общем, «просто попробуйте его с вашим компилятором». приводит к непереносимым программам, которые работают только с вашим компилятором. – Steve Summit 16 February 2016 в 22:26

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

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

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

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

55
ответ дан Christoph 16 August 2018 в 06:48
поделиться
  • 1
  • 2
    @supercat, насколько я знаю i=i=5, также является неопределенным поведением – dhein 23 September 2013 в 16:39
  • 3
  • 4
    ... и компилятор не заметил, что обе записи были в одном и том же месте (если одно или оба значения содержат указатели, которые могут быть трудно определить), сгенерированный код может быть заблокирован. Я не думаю, что реалии реального реализаций реализуют такую ​​блокировку как часть их нормального поведения, но это допустимо в соответствии со стандартом, и если аппаратное обеспечение может реализовать такое поведение дешево, это может быть полезно. На сегодняшнем оборудовании такое поведение было бы слишком дорогостоящим для реализации по умолчанию, но это не значит, что оно всегда будет таким. – supercat 23 September 2013 в 17:19
  • 5
    @supercat, но не допустимо ли правило доступа к точке последовательности только для c99, чтобы объявить его неопределенным поведением? Так что неважно, какое технически оборудование могло бы реализовать? – dhein 23 September 2013 в 17:40

В 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
ответ дан Community 16 August 2018 в 06:48
поделиться

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

Однако, если вы

  • Итак, сначала GCC: использование

    • Итак, сначала 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
    }
    

    Затем переходим к Visual Studio . 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 в Приоритет и порядок оценки :

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

3
ответ дан Muhammad El Nakeep 16 August 2018 в 06:48
поделиться
  • 1
    Я редактировал вопрос, чтобы добавить UB в оценку аргументов функции, так как этот вопрос часто используется в качестве дубликата для этого. (Последний пример) – Antti Haapala 21 October 2017 в 10:46
  • 2
    Также возникает вопрос о c , а не C ++ – Antti Haapala 21 October 2017 в 10:47

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

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

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

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

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

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

13
ответ дан Nikhil Vidhani 16 August 2018 в 06:48
поделиться
  • 1
    Конечно, это не относится к разным переменным в одном выражении. Это было бы полное сбой дизайна, если бы это было! Все, что вам нужно во втором примере, состоит в том, чтобы оба увеличивались между завершением оператора и началом следующего, и это гарантировано именно из-за концепции точек последовательности в центре всего этого. – underscore_d 19 July 2016 в 18:55

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

Первый фрагмент, о котором спросили, 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
ответ дан Steve Summit 16 August 2018 в 06:48
поделиться
  • 1
    Довольно неприятный прием в отношении Undefined Behavior заключается в том, что, хотя он использовал , чтобы быть уверенным в 99,9% компиляторов, чтобы использовать *p=(*q)++; для обозначения if (p!=q) *p=(*q)++; else *p= __ARBITRARY_VALUE;, что больше не так. Для Hyper-modern C потребуется написать что-то вроде последней формулировки (хотя стандартного способа указания кода не волнует, что находится в *p) для достижения уровня эффективности компиляторов, используемых для обеспечения первого (предложение else необходимо, чтобы компилятор оптимизировал if, который потребует некоторые новые компиляторы). – supercat 30 June 2015 в 16:14
  • 2
    На прошлой неделе я видел как минимум 5 похожих вопросов об этих ++ и - безумие. Кажется, это любимая тема некоторых профессоров, чтобы разгадать своих учеников. – artm 8 February 2016 в 08:49

Хотя маловероятно, что какие-либо компиляторы и процессоры действительно это сделают, было бы законно в соответствии со стандартом 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.

22
ответ дан supercat 16 August 2018 в 06:48
поделиться

Большинство ответов здесь цитируется на стандарте C, подчеркивая, что поведение этих конструкций не определено. Чтобы понять, почему поведение этих конструкций не определено, давайте сначала разобрать эти термины в свете стандарта C11:

Последовательность: (5.1.2.3)

При любых двух оценки A и B, если A секвенирован до B, то выполнение A должно предшествовать исполнению B.

Непоследовательность:

Если A не секвенируется до или после B, то A и B не имеют последовательности.

Оценки могут быть одной из двух вещей:

  • вычисления значений, которые выработают результат выражения; и
  • побочные эффекты, которые являются модификациями объектов.

Точка последовательности:

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

Теперь, перейдя к вопросу, для выражений типа

int i = 1;
i = i++;

стандарт говорит, что:

6.5 Выражения:

< blockquote>

Если побочный эффект скалярного объекта не зависит от другого побочного эффекта для одного и того же скалярного объекта или вычисления значения с использованием значения одного и того же скалярного объекта, поведение не определено. [...]

Поэтому вышеприведенное выражение вызывает UB, потому что два побочных эффекта на одном и том же объекте i не имеют последовательности относительно друг друга. Это означает, что он не секвенирован, будет ли побочный эффект назначением i будет выполняться до или после побочного эффекта с помощью ++. В зависимости от того, будет ли выполняться до или после приращения, будут созданы разные результаты, и это один из случаев неопределенного поведения.

Давайте переименуем i слева от назначения be il и справа от присваивания (в выражении i++) будет ir, тогда выражение будет выглядеть как

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Важной точкой в отношении оператора Postfix ++ является то, что:

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

Это означает, что выражение il = ir++ может быть оценено как

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

или

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

, что приводит к двум различным результатам 1 и 2, которые зависят от последовательности побочных эффектов при назначении и ++ и, следовательно, вызывает UB.

42
ответ дан Community 16 August 2018 в 06:48
поделиться

Часто этот вопрос связан как дубликат вопросов, связанных с кодом типа

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

или

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

или схожими вариантами.

Пока это также неопределенное поведение , как уже было сказано, существуют тонкие различия, когда printf() участвует в сравнении с выражением, таким как:

   x = i++ + i++;

В следующем statement:

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

порядок оценки аргументов в printf() является неуказанным . Это означает, что выражения i++ и ++i могут быть оценены в любом порядке. Стандарт C11 содержит некоторые соответствующие описания:

Приложение J, неуказанное поведение

Порядок, в котором обозначение функции, аргументы и подвыражения в аргументах вычисляются в вызове функции (6.5.2.2).

3.4.4, неуказанное поведение

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

ПРИМЕР Пример неуказанного поведения - это порядок, в котором оцениваются аргументы функции.

Неисправное поведение НЕ является проблемой. Рассмотрим этот пример:

printf("%d %d\n", ++x, y++);

Это также имеет неуказанное поведение , потому что порядок оценки ++x и y++ не указан. Но это совершенно законное и достоверное утверждение. В этом утверждении есть неопределенное поведение no undefined. Поскольку модификации (++x и y++) выполняются с различными объектами.

Что делает следующий оператор

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

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


] Еще одна деталь заключается в том, что запятая , участвующая в вызове printf (), является разделителем , а не оператором запятой .

Это важное различие, потому что -коммутатор вводит точку последовательности между оценкой их операндов, что делает следующий законным:

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

Оператор запятой оценивает свои операнды слева направо и дает только значение последнего операнда. Таким образом, в j = (++i, i++); приращения ++i i до 6 и i++ дают старое значение i (6), которое назначено на j. Затем i становится 7 из-за пост-приращения.

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

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

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


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

Это сообщение: Неопределенное, неопределенное и определенное по реализации поведение также актуально.

18
ответ дан Community 16 August 2018 в 06:48
поделиться

Невозможно объяснить поведение, потому что оно вызывает как неопределенное поведение неопределенного поведения , так и неопределенное поведение , поэтому мы не можем делать какие-либо общие предсказания об этом коде, хотя, если вы прочитайте работу Олве Модала, такие как Deep C и Unspecified и Undefined , иногда вы можете делать хорошие догадки в очень конкретных случаях с конкретным компилятором и средой, но пожалуйста, не делайте этого где-нибудь рядом с производством.

Итак, переходим к неуказанному поведению , в черновик c99 standard раздел 6.5 пункт 3 говорит ( emphasis mine ):

Группировка операторов и операндов обозначается синтаксисом.74) За исключением случаев, указанных ниже (для функции -call (), & amp ;, ||,?: и запятые), порядок оценки подвыражений и порядок, в котором происходят побочные эффекты, не определены.

Итак, когда у нас есть такая строка:

i = i++ + ++i;

, мы не знаем будет ли сначала оцениваться i++ или ++i. Это главным образом для того, чтобы дать компилятору лучшие варианты оптимизации .

Здесь также имеется неопределенное поведение , так как программа модифицирует переменные (i ], u и т. д.) более одного раза между точками последовательности . Из проекта стандартного раздела 6.5 пункта 2 ( emphasis mine ):

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

он ссылается на следующие примеры кода как неопределенные:

i = ++i + 1;
a[i++] = i; 

Всего в этих примерах код пытается изменить объект более одного раза в одной и той же точке последовательности, что закончится с ; в каждом из этих случаев:

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

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

Неопределенное поведение определяется в черновик c99 стандарта в разделе 3.4.4 как:

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

и неопределенное поведение определено в разделе 3.4.3 следующим образом:

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

и отмечает, что:

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

42
ответ дан Community 16 August 2018 в 07:52
поделиться
Другие вопросы по тегам:

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