사전 및 사후 증가 정의되지 않은 동작을 사용하는 이러한 구조는 왜됩니까?


814
#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

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

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

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d\n", ++w, w); // shouldn't this print 1 1

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}

12
@Jarett, 아닙니다. "시퀀스 포인트"에 대한 포인터가 필요했습니다. 작업하는 동안 i = i ++로 코드를 찾았습니다. "i의 값을 수정하지 않습니다". 나는 테스트했고 왜 그런지 궁금했다. 그 이후로이 문구를 제거하고 i ++로 대체했습니다.
— PiX 2016 년

198
나는 모든 사람들이 질문자가 구조를 사용하기를 원하기 때문에 항상 이와 같은 질문을한다고 가정하는 것이 흥미 롭습니다. 필자의 첫 번째 가정은 PiX는 이것이 좋지 않다는 것을 알고 있지만 왜 그들이 컴파일러를 사용하고 있는지에 대해 어떻게 행동하는지 궁금하다는 것입니다. 그렇습니다. .. JCF (점프 앤 캐치 파이어) 포함
— Brian Postow

32
궁금합니다 : 컴파일러가 "u = u ++ + ++ u;"와 같은 구문에 대해 경고하지 않는 이유는 무엇입니까? 결과가 정의되지 않은 경우?
— OpenGL을 ES 알아

5
(i++)괄호에 관계없이 여전히 1로 평가됩니다
— Drew McGowen

2
i = (i++);의도 한 것이 무엇이든 , 그것을 작성하는 분명한 방법이 있습니다. 잘 정의 된 경우에도 마찬가지입니다. 의 동작을 정의하는 Java에서도 i = (i++);여전히 잘못된 코드입니다. 그냥 쓰십시오i++;
— Keith Thompson

답변:


566

C에는 정의되지 않은 동작의 개념이 있습니다. 즉, 일부 언어 구문은 구문 상 유효하지만 코드가 실행될 때 동작을 예측할 수 없습니다.

내가 아는 한, 표준은 왜 정의되지 않은 행동의 개념이 존재 하는지 명시하지 않습니다 . 제 생각에는 언어 디자이너가 의미론에 약간의 여유가 있기를 원했기 때문입니다. 즉, 모든 구현이 정확히 동일한 방식으로 정수 오버플로를 처리하도록 요구하기 때문에 심각한 성능 비용을 부과 할 수 있습니다. 정수 오버플로를 일으키는 코드를 작성하면 어떤 일이 발생할 수 있습니다.

그래서, 그 점을 염두에두고 왜 이러한 "문제"가 있습니까? 언어는 특정 사물이 정의되지 않은 행동으로 이어진다 고 분명히 말합니다 . 아무런 문제가 없으며 관련된 "해야한다"는 없습니다. 관련 변수 중 하나가 선언 될 때 정의되지 않은 동작이 변경되면 volatile아무 것도 증명하거나 변경하지 않습니다. 그것은 인 정의 ; 당신은 그 행동에 대해 추론 할 수 없습니다.

가장 흥미로운 예,

u = (u++);

정의되지 않은 동작의 교과서 예제입니다 ( 시퀀스 포인트 에 대한 Wikipedia의 항목 참조 ).


8
@PiX : 여러 가지 이유로 정의되지 않았습니다. 여기에는 다음과 같은 것들이 포함됩니다 : 명확한 "올바른 결과"가없고, 다른 머신 아키텍처는 다른 결과를 강력하게 선호합니다. 기존 관행이 일관성이 없거나 표준 범위를 벗어납니다 (예 : 유효한 파일 이름).
— Richard

모든 사람을 혼동시키기 위해 C11에서는 이러한 예제가 잘 정의되어 있습니다 (예 :) i = ++i + 1;.
— MM

2
UB의 개념이 존재하는 이유는 분명합니다. 이 표준은 C 구현이 특정 목적에 적합하기 위해 수행해야하는 모든 것을 완전하게 설명하기위한 것이 아니며 ( "하나의 프로그램"규칙에 대한 논의 참조) 대신 구현 자의 판단과 유용한 품질 구현을 생성하려는 욕구에 의존합니다. 저수준 시스템 프로그래밍에 적합한 품질 구현은 하이 엔드 번호 크 런칭 애플리케이션에 필요하지 않은 동작의 동작을 정의해야합니다. 표준을 복잡하게하기보다는 ...
— supercat

3
... 모퉁이 사건이 정의되거나 정의되지 않은 것에 대해 극도의 세부 사항을 파악함으로써 표준 작성자는 구현자가 지원해야 할 프로그램의 종류에 따라 어떤 행동이 필요한지 판단 할 수 있도록 더 잘 진행해야 함을 인식했습니다. . 하이퍼 모더니스트 컴파일러는 UB가 특정 프로그램을 만드는 것은 양질의 프로그램이 필요하지 않다는 것을 암시하기위한 것이라고 주장하지만 표준과 이론적 근거는 그러한 의도와 일치하지 않습니다.
— supercat

1
@ jrh : 나는 초 현대주의 철학이 어떻게 손에 닿지 않았는지 깨닫기 전에 그 대답을 썼다. "필요한 플랫폼은 어쨌든이를 지원할 수 있기 때문에 공식적으로이 동작을 인식 할 필요가 없습니다"에서 "이 동작은 인식되지 않았으므로 코드를 인식하지 못했기 때문에이 동작을 제거 할 수 있습니다." 그것을 필요로했다 " 모든 행동이 더 나아졌지 만 그들의 합법성을 인정해야하는 교체를 위해 많은 행동들이 오래 전에 더 이상 사용되지 않아야 했다.
— supercat

78

당신이 얻는 것을 얼마나 정확하게 얻는 지 알고 싶다면 코드 라인을 컴파일하고 분해하십시오.

이것이 내가 생각하는 것과 함께 내 컴퓨터에 얻는 것입니다.

$ 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.

(저는 0x00000014 명령이 일종의 컴파일러 최적화라고 가정합니다.)


기계 코드는 어떻게 얻습니까? 나는 ++ 데브 C 사용하고 난 컴파일러 설정에서 '코드 생성'옵션으로 주위 재생,하지만 별도의 파일 출력 또는 콘솔 출력 갈
— bad_keypoints

5
@ronnieaka gcc evil.c -c -o evil.bin및 gdb evil.bin→ disassemble evil또는 Windows와 동등한 항목은 다음과 같습니다.)
— badp

21
이 답변은의 문제를 실제로 다루지는 않습니다 Why are these constructs undefined behavior?.
— Shafik Yaghmour

9
제쳐두고, 어셈블리로 컴파일하는 것이 더 쉬울 gcc -S evil.c것입니다. 조립 및 분해는 원형 교차로 방식입니다.
— Kat

50
기록을 위해 어떤 이유에서든 주어진 구조가 무엇을하는지 궁금해하는 경우 , 특히 정의되지 않은 동작 일 수 있다는 의심이있는 경우 "컴파일러로 시도하고 참조하십시오"라는 오래된 조언은 다음과 같습니다. 잠재적으로 매우 위험합니다. 최선을 다해 학습합니다.이러한 상황에서 오늘날이 컴파일러 버전 . 당신은 할 수 없습니다 그것이 보장 기능에 대한 아무것도 할 경우 많은 것을 배운다. 일반적으로 "컴파일러로 시도해보십시오"는 컴파일러에서만 작동하는 이식 불가능한 프로그램으로 이어집니다.
— Steve Summit

64

C99 표준의 관련 부분은 6.5 식, §2라고 생각합니다.

이전과 다음 시퀀스 포인트 사이에서 객체는 표현식의 평가에 의해 저장된 값을 최대 한 번 수정해야합니다. 또한, 저장 될 값을 결정하기 위해 이전 값은 읽기 전용이어야한다.

6.5.16 할당 연산자, §4 :

피연산자의 평가 순서는 지정되어 있지 않습니다. 할당 연산자의 결과를 수정하거나 다음 시퀀스 포인트 이후에 액세스하려고하면 동작이 정의되지 않습니다.


2
위의 내용은 'i = i = 5; "가 정의되지 않은 행동이라는 것을 의미합니까?
— supercat

1
내가 아는 한 i=i=5
— @supercat

2
@Zaibis : 대부분의 장소에서 사용하고 싶은 이론적 근거는 이론적으로 다중 프로세서 플랫폼이 A=B=5;"쓰기 잠금 A; 쓰기 잠금 B; 저장소 5-A; 저장소 5-B; 잠금 해제 B" 와 같은 것을 구현할 수 있음을 적용합니다 . ; Unock A; "및 C=A+B;"읽기 잠금 A; 읽기 잠금 B; 컴퓨팅 A + B; 잠금 해제 A 및 B; 쓰기 잠금 C; 저장 결과; 잠금 해제 C; " 와 같은 문장 . 그러면 한 스레드가 A=B=5;다른 스레드를 수행 한 경우 다른 스레드가 수행 C=A+B;한 경우 두 쓰기가 모두 발생한 것으로 보거나 둘 다 보지 못하게됩니다. 잠재적으로 유용한 보증입니다. 그러나 하나의 스레드가 I=I=5;...
— supercat

1
... 그리고 컴파일러는 두 쓰기가 모두 같은 위치에 있다는 것을 알지 못했습니다 (하나 또는 두 개의 lvalue에 포인터가 포함되어 결정하기 어려울 수 있음). 생성 된 코드가 교착 상태에 빠질 수 있습니다. 실제 구현에서는 일반적인 동작의 일부로 이러한 잠금을 구현한다고 생각하지 않지만 표준에서는 허용되며 하드웨어가 그러한 동작을 저렴하게 구현할 수 있다면 유용 할 수 있습니다. 오늘날의 하드웨어에서 이러한 동작은 기본값으로 구현하기에는 너무 비싸지 만 항상 그렇게되는 것은 아닙니다.
— supercat

1
@ supercat이지만 c99의 시퀀스 포인트 액세스 규칙만으로는 정의되지 않은 동작으로 선언하기에 충분하지 않습니까? 하드웨어가 기술적으로 구현할 수있는 것은 중요하지 않습니까?
— dhein

55

여기에 나오는 대부분의 답변은 C 표준에서 인용 한 것으로, 이러한 구성의 동작이 정의되어 있지 않음을 강조합니다. 이러한 구성의 동작이 정의되지 않은 이유 를 이해하려면 C11 표준에 비추어 먼저이 용어를 이해하십시오.

순서 : (5.1.2.3)

두 가지 평가가 주어 A지고 이전 에 시퀀싱 된 B경우 의 실행이 의 실행보다 우선한다 . ABAB

순차 :

이 A전후에 시퀀싱 되지 않은 B경우 A및 순서가 지정되지 않습니다 B.

평가는 다음 두 가지 중 하나 일 수 있습니다.

  • 식의 결과를 산출 하는 값 계산 ; 과
  • 부작용 은 객체의 수정입니다.

시퀀스 포인트 :

식의 계산 사이의 시퀀스 지점이 존재 A하고 B암시하는 모든 값을 계산 하고 부작용 과 관련된 A모든 이전 순서화되는 값 계산 및 부작용 과 연관된 B.

이제 질문에옵니다.

int i = 1;
i = i++;

표준은 말합니다 :

6.5 표현 :

스칼라 객체의 부작용에 대하여 unsequenced 경우 중 동일한 스칼라 객체에서 다른 부작용 또는 동일한 객체의 스칼라 값을 이용하여 값의 계산은, 동작이 정의되지 않는다 . [...]

따라서 동일한 오브젝트에 대한 두 개의 부작용 i이 서로에 대해 순서가 다르기 때문에 위의 표현식은 UB를 호출합니다 . 즉, 할당에 의한 부작용이 부작용의 i전후 인지에 따라 순서가 결정되지 않습니다 ++.
할당이 증분 이전 또는 이후에 발생하는지에 따라 다른 결과가 생성되며 이는 정의되지 않은 동작 의 경우 중 하나입니다. .

i대입 왼쪽의 이름 을 대입 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를 호출한다.


52

지정되지 않은 동작 과 정의되지 않은 동작을 모두 호출하기 때문에 동작을 설명 할 수 없으므로 Deep C 및 Unspecified 및 Undefined 와 같은 Olve Maudal의 작업 을 읽으면 때로는 좋은 결과를 얻을 수 있지만이 코드에 대한 일반적인 예측을 할 수는 없습니다. 특정 컴파일러와 환경에서 매우 구체적인 경우를 추측하지만 프로덕션 근처에서는 그렇게하지 마십시오.

로 이동 그래서 수없는 동작 에서, 표준 C99 초안 섹션 6.5제 3은 말한다 ( 강조 광산 ) :

연산자 및 피연산자들의 그룹핑은 (함수 호출 (용) 나중에 지정된 제외)로 표시된다 syntax.74, &&, ||, :, 및 콤마 연산자) 표현식의 평가 순서 및 순서 어떤 부작용이 발생하는지는 명시되어 있지 않습니다.

따라서 다음과 같은 줄이있을 때 :

i = i++ + ++i;

우리는 여부를 알 수없는 i++또는 ++i먼저 평가됩니다. 이것은 주로 컴파일러에게 최적화를위한 더 나은 옵션을 제공하기위한 것 입니다.

우리는 또한이 정의되지 않은 동작을 프로그램 변수를 수정 (때문에 여기뿐만 아니라 i, u사이에 한 번 이상, 등) 순서 포인트 . 초안 표준 섹션에서 6.5제 2 ( 강조 광산 ) :

이전과 다음 시퀀스 포인트 사이에서 객체는 표현식의 평가에 의해 저장된 값을 최대 한 번 수정해야 합니다. 또한, 저장 될 값을 결정하기 위해 이전 값은 읽기 전용이어야한다 .

다음 코드 예제는 정의되지 않은 것으로 인용합니다.

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로서 :

휴대 할 수 없거나 잘못된 프로그램 구조 또는 잘못된 데이터를 사용할 때의 행동,이 표준에 요구 사항이없는 경우

그리고 메모 :

정의되지 않은 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것, 환경의 특징적인 문서화 된 방식으로 진단 또는 프로그램 실행 중 (진단 메시지 발행 여부에 관계없이), 번역 또는 실행 종료 (발급 포함)에 이르기까지 다양합니다. 진단 메시지).


33

시퀀스 포인트와 정의되지 않은 동작의 세부적인 세부 사항에 얽매이지 않고 이것을 대답하는 또 다른 방법은 단순히 무엇을 의미해야하는지 묻는 것입니다. 프로그래머는 무엇을하려고 했습니까?

에 대한 첫 번째 조각 i = i++ + ++i은 내 책에서 분명히 명백히 미쳤다. 아무도 실제 프로그램에서 그것을 작성하지 않았으며, 그것이 무엇을하는지 명확하지 않으며, 누군가가이 특별한 계획된 조작 순서를 초래할 수있는 코딩을 시도했을 수도있는 알고리즘도 없습니다. 그리고 당신과 나에게해야 할 일이 분명하지 않기 때문에 컴파일러가해야 할 일을 이해할 수 없다면 내 책에서 괜찮습니다.

두 번째 조각 i = i++은 이해하기 조금 더 쉽습니다. 누군가가 분명히 i를 증가시키고 결과를 i에 다시 할당하려고합니다. 그러나 C에서 이것을 수행하는 몇 가지 방법이 있습니다. 1을 i에 더하고 결과를 다시 i에 할당하는 가장 기본적인 방법은 거의 모든 프로그래밍 언어에서 동일합니다.

i = i + 1

물론 C에는 편리한 단축키가 있습니다.

i++

이는 "i에 1을 추가하고 결과를 i에 다시 할당"을 의미합니다. 그래서 우리가이 두 가지를 만들어서

i = i++

우리가 실제로 말하는 것은 "i에 1을 더하고 결과를 다시 i에 할당하고 결과를 다시 i에 할당"입니다. 우리는 혼란스러워서 컴파일러가 혼란스러워도 너무 귀찮게하지 않습니다.

현실적으로,이 미친 표현은 사람들이 ++가 어떻게 작동해야하는지에 대한 인공적인 예로 사용하는 경우에만 가능합니다. 물론 ++ 작동 방식을 이해하는 것이 중요합니다. 그러나 ++를 사용하는 실질적인 규칙 중 하나는 "++를 사용한 표현의 의미가 확실하지 않으면 쓰지 마십시오"입니다.

우리는 comp.lang.c에서 이와 같은 표현과 왜 정의되지 않은지에 대해 많은 시간을 보냈습니다 . 이유를 설명하려는 더 긴 두 가지 답변이 웹에 보관됩니다.

질문 3.8 및 C FAQ 목록 의 섹션 3 에있는 나머지 질문 도 참조하십시오 .


1
Undefined Behavior와 관련하여 다소 불쾌한 점 은 컴파일러의 99.9 % 가 더 이상 그렇지 않다는 것을 의미 하는 데 사용 하는 것이 안전 *p=(*q)++;하다는 if (p!=q) *p=(*q)++; else *p= __ARBITRARY_VALUE;것입니다. 하이퍼 현대 C (투입 무슨 상관하지 않는 코드를 나타내는의 표준 방법이 없지만 후자의 공식 같은 것을 쓰고 필요 *p) 이전합니다 (함께 제공하는 데 사용 효율 컴파일러의 수준을 달성 할 else수 있도록하기 위해 필요한 조항을 if컴파일러는 일부 최신 컴파일러에 필요한 것을 최적화 합니다).
— supercat

@supercat 나는 이제 그런 종류의 최적화를 수행하기에 충분히 "똑똑한"컴파일러도 assert명령문 을 들여다 볼 수있을만큼 똑똑해야 프로그래머가 문제의 행을 간단한 것으로 선행 할 수 있다고 믿는다 assert(p != q). (물론,이 과정을 수강 <assert.h>하려면 디버그 버전이 아닌 버전에서 어설 션을 완전히 삭제하지 않고 __builtin_assert_disabled()컴파일러에서 알 수있는 코드로 변환 한 다음 코드를 생성하지 않는 것으로 바꾸어야합니다.)
— Steve Summit

25

종종이 질문은 다음과 같은 코드와 관련된 질문의 중복으로 연결됩니다

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

또는

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

또는 유사한 변형.

이것은 이미 언급 한 바와 같이 정의되지 않은 동작 이지만 , 다음 printf()과 같은 명령문과 비교할 때 미묘한 차이 가 있습니다.

x = i++ + i++;

다음 진술에서 :

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++지정되지 않습니다. 그러나 그것은 완벽하게 합법적이고 유효한 진술입니다. 없습니다 에는 이 문장에서 정의되지 않은 동작이. 수정 ( ++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 에서 정의되지 않은 행동의 다른 많은 변형을 이해하면 도움이 될 것입니다

이 게시물 : 정의되지 않은 지정되지 않은 구현 정의 동작 도 관련이 있습니다.


이 시퀀스 int a = 10, b = 20, c = 30; printf("a=%d b=%d c=%d\n", (a = a + b + c), (b = b + b), (c = c + c));는 안정적인 동작을 제공하는 것으로 보입니다 (gcc v7.3.0에서 오른쪽에서 왼쪽으로의 인수 평가; 결과 "a = 110 b = 40 c = 60"). 과제가 '완전한 진술'로 간주되어 시퀀스 포인트가 도입 되었기 때문입니까? 그 결과로 왼쪽에서 오른쪽으로의 논쟁 / 문구 평가가 이루어져서는 안됩니까? 아니면 정의되지 않은 행동의 징후일까요?
— kavadias

@kavadias printf 문은 위에서 설명한 것과 같은 이유로 정의되지 않은 동작을 포함합니다. 당신이 작성 b하고 c제 3 및 제 4 인수에 각각, 2 인수에 읽기. 그러나이 식 (2, 3, 4 번째 인수) 사이에는 시퀀스가 ​​없습니다. gcc / clang에는 -Wsequence-point이것들을 찾는데 도움 이되는 옵션 이 있습니다.
— PP

23

컴파일러와 프로세서가 실제로 그렇게 할 가능성은 낮지 만 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

프로세서가 그러한 일을 효율적으로 수행 할 수 있도록 하드웨어를 지원한다고 생각하지는 않지만 그러한 행동으로 인해 멀티 스레드 코드를 더 쉽게 만드는 상황을 쉽게 상상할 수 있습니다 (예 : 두 스레드가 위의 작업을 수행하려고하면 보장 할 것입니다) 순서는 동시에 i2 씩 증가합니다) 그리고 미래의 일부 프로세서가 이와 같은 기능을 제공 할 수는 없습니다.

컴파일러가 i++위에 표시된대로 (표준에 따라 합법적으로) 작성하고 전체 표현의 평가를 통해 위의 지침을 산재하고 (법적) 다른 지침 중 하나가 발생했음을 알 수없는 경우 액세스 i하려면 컴파일러가 교착 상태에 빠질 수있는 일련의 명령어를 생성 할 수 있습니다. 확인하려면, 컴파일러는 거의 확실 같은 변수가 경우에 문제를 감지 할 i두 곳 모두에 사용됩니다,하지만 루틴은 두 개의 포인터 참조를 허용하는 경우 p와 q, 그리고 사용 (*p)및(*q) 위의 식 (보다는 사용의를i두 번) 컴파일러는 동일한 객체의 주소가 둘 다에 전달되면 교착 상태를 인식하거나 피할 필요가 없습니다 .p 및q


16

그동안 구문 표현의 좋아하는 a = 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;

다양한 시퀀스 포인트가 C11 (및 C99 ) 의 부록 C에 나열되어 있습니다 .

  1. 다음은 5.1.2.3에 설명 된 시퀀스 포인트입니다.

    • 함수 지정자의 평가와 함수 호출의 실제 인수 및 실제 호출 사이. (6.5.2.2).
    • 다음 연산자의 첫 번째 피연산자와 두 번째 피연산자의 평가 사이 : 논리 AND && (6.5.13); 논리 OR || (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)의 각각의 (선택적) 표현; 리턴 명령문의 (선택 사항) 표현식 (6.8.6.4)
    • 라이브러리 함수가 (7.1.4)를 반환하기 직전에.
    • 형식화 된 각 입력 / 출력 기능 변환 지정자와 연관된 조치 후 (7.21.6, 7.29.2)
    • 비교 함수에 대한 각 호출 직전과 직후, 그리고 비교 함수에 대한 호출과 해당 호출에 대한 인수로 전달 된 객체의 이동 사이 (7.22.5).

C11 에서 동일한 단락 의 문구는 다음과 같습니다.

  1. 스칼라 오브젝트의 부작용이 동일한 스칼라 오브젝트의 다른 부작용 또는 동일한 스칼라 오브젝트의 값을 사용한 값 계산과 관련하여 순서가 지정되지 않으면 동작이 정의되지 않습니다. 표현식의 하위 표현식에 허용되는 순서가 여러 개인 경우 순서에 상관없이 이러한 부작용이 발생하면 동작이 정의되지 않습니다 .84)

예를 들어 -Walland 와 함께 최신 버전의 GCC를 사용하여 프로그램에서 이러한 오류를 감지 -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);

잘 정의되어 있으며 i1 씩 증가 하여 이전 값을 산출하고 해당 값을 버립니다. 그런 다음 쉼표 연산자에서 부작용을 해결하십시오. i1 씩 증가 하면 결과 값이 표현식의 값이됩니다. 즉, 이것은 j = (i += 2)다시 쓰기에 대한 "영리한"방법 인 작성하기 위해 고안된 방법입니다.

i += 2;
j = i;

그러나 ,함수 내 인수 목록은 쉼표 연산자 가 아니며 별개의 인수 평가 사이에 시퀀스 포인트가 없습니다. 대신 그들의 평가는 서로에 대해 순서가 정해지지 않습니다. 그래서 함수 호출

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

보유 정의되지 않은 동작을 하기 때문에 거기의 평가 사이 시퀀스 지점 없다 i++및 ++i함수 인수는 및의 값은 i그러므로 모두 두번 수정 i++과 ++i이전 및 다음 시퀀스 포인트 간의.


14

C 표준에 따르면 변수는 두 시퀀스 포인트 사이에 최대 한 번만 할당해야합니다. 예를 들어 세미콜론은 시퀀스 포인트입니다.
따라서 양식의 모든 진술 :

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

그래서 그 규칙을 위반합니다. 이 표준은 또한 행동이 정의되지 않았으며 지정되지 않았다고 말합니다. 일부 컴파일러는이를 감지하여 일부 결과를 생성하지만 이는 표준에 맞지 않습니다.

그러나 두 시퀀스 포인트간에 서로 다른 두 변수를 증분시킬 수 있습니다.

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

위의 내용은 문자열을 복사 / 분석하는 동안 일반적인 코딩 방법입니다.


물론 하나의 표현식 내에서 다른 변수에는 적용되지 않습니다. 만약 그렇다면 전체 설계 실패 일 것입니다! 두 번째 예제에서 필요한 것은 명령문 끝과 다음 시작 사이에 증가하는 것입니다.이 모든 것은 중심의 시퀀스 포인트 개념으로 인해 보장됩니다.
— underscore_d

11

에서 /programming/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

현대의 컴파일러는 이것을 잘 최적화합니다. 실제로, 원래 작성한 코드보다 더 좋을 수 있습니다 (원하는 방식으로 작동했다고 가정).


5

계산의이 종류에서 일어나는 일에 대해 좋은 설명은 문서에서 제공되는 n1188 로부터 는 ISO W14 사이트 .

나는 아이디어를 설명한다.

이 상황에 적용되는 표준 ISO 9899의 주요 규칙은 6.5p2입니다.

이전과 다음 시퀀스 포인트 사이에서 객체는 표현식의 평가에 의해 저장된 값을 최대 한 번 수정해야합니다. 또한, 저장 될 값을 결정하기 위해 이전 값은 읽기 전용이어야한다.

식의 시퀀스 포인트 i=i++는 이전 i=과 이후 i++입니다.

위에서 인용 한 논문에서는 프로그램이 작은 상자로 구성되는 것으로 파악할 수 있다고 설명합니다. 각 상자에는 두 개의 연속적인 시퀀스 지점 사이의 명령이 들어 있습니다. 서열 점은 표준의 부속서 C에 정의되어 i=i++있으며, 완전한 발현을 한정하는 2 개의 서열 점이 있는 경우 . 이러한 표현은 문법 expression-statement의 Backus-Naur 형태의 입력과 문법적으로 동일 합니다 (문법은 표준의 부록 A에 제공됨).

따라서 상자 안의 지시 순서는 명확한 순서가 아닙니다.

i=i++

로 해석 될 수있다

tmp = i
i=i+1
i = tmp

또는

tmp = i
i = tmp
i=i+1

코드를 해석하기위한이 두 가지 형식이 모두 i=i++유효하고 다른 응답을 생성하기 때문에 동작이 정의되지 않습니다.

따라서 프로그램을 구성하는 각 상자의 시작과 끝에서 시퀀스 포인트를 볼 수 있습니다 (상자는 C의 원자 단위 임). 상자 안의 지시 순서는 모든 경우에 정의되지 않습니다. 순서를 변경하면 결과가 변경 될 수 있습니다.

편집하다:

이러한 모호성을 설명하는 또 다른 좋은 출처는 c-faq 사이트 ( 도서로 도 게시 됨 )의 항목, 즉 여기 와 여기 및 여기 입니다.


이 답변이 기존 답변에 어떻게 새로 추가 되었습니까? 또한 이에 대한 설명 i=i++은 이 답변 과 매우 유사합니다 .
— haccks

@ haccks 나는 다른 답변을 읽지 못했습니다. 나는 ISO 9899의 공식 사이트에서 언급 한 문서에서 배운 것을 내 자신의 언어로 설명하고 싶었 open-std.org/jtc1/sc22/wg14/www/docs/n1188.pdf
— alinsoar

5

귀하의 질문은 아마도 "왜 이러한 구조가 C에서 정의되지 않은 동작입니까?" 귀하의 질문은 아마도 "이 코드 ()를 사용하여 ++예상 한 값을 얻지 못한 이유는 무엇 입니까?"였으며 누군가 귀하의 질문을 중복으로 표시하여 여기로 보냈습니다.

이 답변은 그 질문에 답하려고합니다 : 왜 당신의 코드가 당신에게 기대 한 대답을주지 않았으며, 예상대로 작동하지 않을 표현을 인식하고 피하는 법을 배울 수 있습니다.

나는 지금까지 C ++와 --연산자 의 기본 정의를 듣고 접두사 형식 ++x이 postfix 형식 과 어떻게 다른지 들었습니다 x++. 그러나 이러한 연산자는 생각하기가 어렵습니다. 이해를 돕기 위해 아마도 다음과 같은 작은 테스트 프로그램을 작성했을 것입니다.

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

그러나 놀랍게도이 프로그램은 당신이 이해하는 데 도움 이 되지 않았습니다. 이상하고, 예상치 못한, 설명 할 수없는 결과물을 인쇄 ++했습니다.

또는 아마도 이해하기 어려운 표현을보고있을 것입니다

int x = 5;
x = x++ + ++x;
printf("%d\n", x);

아마도 누군가 당신에게 그 코드를 퍼즐로 줬을 것입니다. 이 코드는 또한 특히 코드를 실행하는 경우 의미가 없으며 두 개의 다른 컴파일러에서 컴파일하고 실행하면 두 가지 다른 응답을 얻을 수 있습니다! 무슨 일이야? 어떤 대답이 맞습니까? (그리고 대답은 둘 다 또는 둘 다 없다는 것입니다.)

지금까지 들었 듯이,이 모든 표현들은 정의 되어 있지 않습니다 . 즉, C 언어는 그들이 할 일을 보증하지 않습니다. 컴파일하고 실행하는 한 작성할 수있는 모든 프로그램이 고유하고 잘 정의 된 출력을 생성 할 것이라고 생각했기 때문에 이것은 이상하고 놀라운 결과입니다. 그러나 정의되지 않은 동작의 경우에는 그렇지 않습니다.

식이 정의되지 않은 이유는 무엇입니까? 관련된 표현이 있습니까++와-- 항상 정의되지 않습니까? 물론 아닙니다 : 이들은 유용한 연산자이며 제대로 사용하면 완벽하게 정의됩니다.

우리가 표현에 대해 정의하지 않은 표현은 한 번에 너무 많은 일이 발생하는 경우, 어떤 일이 일어날 지 확실하지 않지만 결과가 중요한 순서 일 때입니다.

이 답변에서 사용한 두 가지 예로 돌아가 보겠습니다. 내가 쓸 때

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

문제는, 호출하기 전에되고 printf, 컴파일러의 값을 계산 않습니다 x첫째, 나 x++, 아니면 ++x? 그러나 우리는 모른다 . C에는 함수에 대한 인수가 왼쪽에서 오른쪽으로, 오른쪽에서 왼쪽으로 또는 다른 순서로 평가된다는 규칙이 없습니다. 우리는 컴파일러가 할 것입니다 여부를 말할 수 없다 그래서 x첫째, 다음 ++x, 다음 x++, 또는 x++다음 ++x다음 x, 또는 다른 순서. 그러나 순서는 분명히 중요합니다. 컴파일러가 사용하는 순서에 따라 다른 결과가 명확하게 인쇄되기 때문입니다.printf 입니다.

이 미친 표현은 어떻습니까?

x = x++ + ++x;

이 표현의 문제는 x의 값 수정할 수있는 세 가지 다른 시도 포함되어있다 : (1) x++부분의 시도는, X 1 추가에 새 값을 저장하기 위해 x, 그리고 이전 값을 반환을 x; (2) ++x부품이 x에 1을 더하고에 새 값을 저장하고 새 값을 x반환 하려고합니다 x. 그리고 (3) x =파트는 다른 두 개의 합계를 다시 x에 할당하려고 시도합니다. 이 세 번의 과제 중 어느 것이 "승리"할 것인가? 세 값 중 어느 것이 실제로 할당 x됩니까? 다시 말하지만 놀랍게도 C에는 우리에게 말할 규칙이 없습니다.

우선 순위 또는 연관성 또는 왼쪽에서 오른쪽으로 평가하면 어떤 순서로 발생하는지 알 수 있지만 그렇지 않습니다. 당신은 저를 믿지 않을 수도 있지만, 제 말을 들어주세요. 다시 말하겠습니다 : 우선 순위와 연관성은 C에서 표현식의 평가 순서의 모든 측면을 결정하지 않습니다. 특히, 하나의 표현식 내에 여러 개가 있다면 x, 우선 순위 및 연관성에 새로운 값을 할당하려고 시도하는 다른 지점 은 이러한 시도 중 어느 것이 처음 또는 마지막으로 발생하는지 또는 다른 것을 알려주지 않습니다 .


따라서 모든 배경과 소개를 벗어난 상태에서 모든 프로그램이 잘 정의되어 있고 어떤 표현을 작성할 수 있고 어떤 표현을 쓸 수 없는지 확인하려면 어떻게해야합니까?

이 표현들은 모두 괜찮습니다.

y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;

이 표현식은 모두 정의되어 있지 않습니다.

x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);

마지막 질문은 어떤식이 잘 정의되어 있고 어떤식이 정의되어 있지 않은지 어떻게 알 수 있습니까?

앞에서 말했듯이 정의되지 않은 표현식은 한 번에 너무 많은 일이 발생하는 순서, 무슨 일이 발생했는지, 순서가 중요한 부분을 나타내는 표현입니다.

  1. 둘 이상의 다른 위치에서 수정 (할당)되는 변수가 하나 있으면 어떤 수정이 먼저 발생하는지 어떻게 알 수 있습니까?
  2. 한 곳에서 수정되고 다른 곳에서 값을 사용하는 변수가있는 경우 이전 값을 사용하는지 새 값을 사용하는지 어떻게 알 수 있습니까?

식에서 # 1의 예로서

x = x++ + ++x;

`x를 수정하려는 세 가지 시도가 있습니다.

# 2의 예로서, 표현에서

y = x + x++;

우리는 모두의 값을 사용 x하고 수정합니다.

이것이 바로 답입니다. 작성한 표현식에서 각 변수는 최대 한 번 수정되고 변수가 수정 된 경우 해당 변수의 값을 다른 곳에서도 사용하려고 시도하지 마십시오.


3

그 이유는 프로그램이 정의되지 않은 동작을 실행하고 있기 때문입니다. 문제는 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 (사전 증가)가 먼저 평가 된 다음 c 값이 연산에 사용 된 후 사후 증가 c ++)가 사용됩니다.

GCC C ++ 에 따르면 : 연산자

GCC C ++에서 연산자의 우선 순위는 개별 연산자가 평가되는 순서를 제어합니다.

GCC가 이해하는 정의 된 동작 C ++의 해당 코드 :

#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 
}

Visual Studio는 어떻게 작동하고 다른 접근 방식을 취하며 첫 번째 단계에서 모든 사전 증분 식을 평가 한 다음 두 번째 단계에서 연산의 변수 값을 사용하고 세 번째 단계에서 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 설명서가 명시되어 있습니다 .

여러 연산자가 함께 나타나는 경우 우선 순위가 같고 연관성에 따라 평가됩니다. 표의 연산자는 접미사 연산자로 시작하는 섹션에 설명되어 있습니다.


1
함수 인수를 평가할 때 UB를 추가하기 위해 질문을 편집했습니다.이 질문은 종종 복제본으로 사용되기 때문입니다. (마지막 예)
— Antti Haapala

1
또한 질문은 C ++가 아니라 c 에 관한 것입니다
— Antti Haapala
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.