[c++] 정의되지 않은 동작 및 시퀀스 포인트

“시퀀스 포인트”란 무엇입니까?

정의되지 않은 동작과 시퀀스 포인트의 관계는 무엇입니까?

나는 종종 a[++i] = i;기분이 나아지도록 재미 있고 복잡한 표현을 사용 합니다. 왜 사용을 중단해야합니까?

이 내용을 읽은 경우 후속 질문 Undefined behavior and sequence points reloaded 를 방문하십시오 .


(참고 : 이것은 Stack Overflow의 C ++ FAQ에 대한 항목 입니다.이 양식으로 FAQ를 제공한다는 아이디어를 비판하려면이 모든 것을 시작한 메타에 게시 하면됩니다. 이 질문은 C ++ 대화방 에서 모니터링되며 여기서 FAQ 아이디어는 처음부터 시작되었으므로 아이디어를 얻은 사람들이 대답을 읽을 가능성이 큽니다.)



답변

C ++ 98 및 C ++ 03

이 답변은 이전 버전의 C ++ 표준에 대한 것입니다. 표준의 C ++ 11 및 C ++ 14 버전은 공식적으로 ‘시퀀스 포인트’를 포함하지 않습니다. 대신에 ‘시퀀스 이전’또는 ‘시퀀스되지 않은’또는 ‘불확실하게 시퀀싱’됩니다. 순 효과는 본질적으로 동일하지만 용어는 다릅니다.


면책 조항 : 알겠습니다. 이 답변은 약간 깁니다. 읽는 동안 인내심을 가지십시오. 이미 알고 있다면 다시 읽어도 미치지 않습니다.

전제 조건 : C ++ 표준에 대한 기초 지식


시퀀스 포인트는 무엇입니까?

표준은 말한다

실행 순서에서 시퀀스 포인트 라고하는 특정 특정 지점에서 이전 평가의 모든 부작용 이 완료되고 후속 평가의 부작용 이 발생 하지 않아야합니다 . (§1.9 / 7)

부작용? 부작용은 무엇입니까?

식의 평가는 무언가를 생성하며, 실행 환경의 상태에 변화가 있다면, 그 식 (평가)은 부작용이 있다고합니다.

예를 들면 다음과 같습니다.

int x = y++; //where y is also an int

초기화 작업 외에도 작업자 y의 부작용으로 인해 값 이 변경 ++됩니다.

여태까지는 그런대로 잘됐다. 시퀀스 포인트로 넘어갑니다. comp.lang.c 저자가 제공 한 seq-points의 대체 정의 Steve Summit:

시퀀스 포인트는 먼지가 정착 된 시점이며 지금까지 본 모든 부작용이 완벽하게 보장됩니다.


C ++ 표준에 나열된 공통 시퀀스 포인트는 무엇입니까?

사람들은:

  • 전체 표현식의 평가가 끝날 때 ( §1.9/16) (전체 표현식은 다른 표현식의 하위 표현식이 아닌 표현식입니다.) 1

    예 :

    int a = 5; // ; is a sequence point here
  • 첫 번째 식 ( §1.9/18) 2 의 평가 후 다음 각 식의 평가에서

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18)(여기서, B는 콤마 연산자이다에서 func(a,a++) ,콤마 연산자 아니라, 단지 인자 사이의 구분자 aa++. 따라서, 동작이 (이 경우에 정의되어 있지 않으면 a기본 유형으로 간주된다))
  • 함수 호출 (함수가 인라인인지 여부)에서 함수 본문 ( §1.9/17) 에서 표현식 또는 명령문을 실행하기 전에 발생하는 모든 함수 인수 (있는 경우)를 평가 한 후

1 : 참고 : 전체 표현의 평가에는 전체 표현의 어휘 부분이 아닌 하위 표현의 평가가 포함될 수 있습니다. 예를 들어, 기본 인수 표현식 (8.3.6)을 평가하는 데 포함 된 하위 표현식은 기본 인수를 정의하는 표현식이 아니라 함수를 호출하는 표현식에서 작성된 것으로 간주됩니다.

2 : 표시된 연산자는 5 절에 설명 된 내장 연산자입니다. 이러한 연산자 중 하나가 유효한 컨텍스트에서 오버로드되어 (13 절) 사용자 정의 연산자 함수를 지정하면 표현식은 함수 호출을 지정합니다. 피연산자는 그들 사이에 내재 된 순서 지점없이 인수 목록을 형성합니다.


정의되지 않은 행동이란 무엇입니까?

표준은 섹션에서 정의되지 않은 동작을 다음 §1.3.12과 같이 정의합니다.

잘못된 프로그램 구조 또는 잘못된 데이터를 사용할 때 발생할 수있는 행동 (이 국제 표준에 요구 사항 없음) 3 .

이 국제 표준이 행동의 명시 적 정의에 대한 설명을 생략 할 때 정의되지 않은 행동이 예상 될 수도 있습니다.

3 : 허용되지 않는 정의 된 동작은 예측할 수없는 결과로 상황을 완전히 무시하는 것부터, 환경의 문서화 된 방식으로 진단 또는 프로그램 실행 중 (진단 메시지 발행 여부에 관계없이), 번역 또는 실행 종료까지 (진단 메시지 발행).

즉, 정의되지 않은 동작은 코에서 날아 오는 데몬에서 여자 친구가 임신하는 것에 이르기까지 모든 일이 발생할 수 있음을 의미 합니다.


정의되지 않은 동작과 시퀀스 포인트의 관계는 무엇입니까?

들어가기 전에 Undefined Behaviour, Unspecified Behavior 및 Implementation Defined Behavior 의 차이점을 알아야합니다 .

또한 그 사실을 알아야합니다 the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified.

예를 들면 다음과 같습니다.

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

여기에 또 다른 예가 있습니다 .


이제 표준은 §5/4말합니다

  • 1) 이전 시퀀스 포인트와 다음 시퀀스 포인트 사이에서 스칼라 객체는 표현식의 평가에 의해 저장된 값을 최대 한 번 수정해야합니다.

무슨 뜻이에요?

공식적으로 두 시퀀스 포인트 사이에서 변수를 두 번 이상 수정해서는 안됩니다. 표현 문에서는 next sequence point일반적으로 종료 세미콜론에 있고 previous sequence point이전 명령문의 끝에 있습니다. 표현식은 중간을 포함 할 수도 있습니다 sequence points.

위 문장에서 다음 표현식은 정의되지 않은 동작을 호출합니다.

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

그러나 다음 표현은 괜찮습니다.

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) 또한, 이전 값은 저장 될 값을 결정하기 위해서만 액세스되어야한다.

무슨 뜻이에요? 이는 전체 표현식 내에서 오브젝트를 작성하는 경우 동일한 표현식 내에서 오브젝트에 대한 모든 액세스는 작성 될 값 계산에 직접 포함되어야 함을 의미합니다 .

예를 들어 (LHS 및 RHS에서의) i = i + 1모든 접근 i에서 기록 될 값의 계산직접 관여 합니다. 그래서 괜찮습니다.

이 규칙은 법적 표현이 접근이 수정 전에 명백하게 적용되는 표현으로 효과적으로 제한합니다.

예 1 :

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

예 2 :

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

의 액세스 i중 하나가 a[i]i에 저장된 값과 관련이 없기 때문에 허용되지 않으므로 (또는에서 발생 함 i++) 우리의 이해 또는 컴파일러-증가 된 값이 저장되기 전후에 액세스가 수행되어야하는지 여부. 따라서 동작은 정의되지 않습니다.

예 3 :

int x = i + i++ ;// Similar to above

여기 에서 C ++ 11에 대한 답변을 따르십시오 .


답변

이것은 이전 답변에 대한 후속 조치 이며 C ++ 11 관련 자료를 포함합니다. .


선수 과목 : 관계 (수학)에 대한 기초 지식.


C ++ 11에 시퀀스 포인트가 없다는 것이 사실입니까?

예! 이것은 매우 사실입니다.

C ++ 11에서 Sequence PointsSequenced BeforeSequenced After (및 Unsequenced and Indeterminately Sequenced ) 관계 로 대체되었습니다 .


이 ‘시퀀스 이전’은 정확히 무엇입니까?

Sequenced Before (§1.9 / 13) 는 다음과 같은 관계입니다.

단일 스레드에 의해 실행되는 평가 사이 에 엄격한 부분 순서를 유도합니다 1

공식적으로 그것은 두 가지 평가 (아래 참조)가 주어진다는 것을 의미 A하며 B, 만약 이전순서A정해져 있다면 B,의 실행이 의 실행 A 보다 우선해야한다B . 경우 A이전 서열되지 BB이전 순서가되지 않고 A, 다음 AB이다 unsequenced 2 .

평가 A하고 B있다 불확실 서열 하나가 될 때 A전에 서열화 B또는 B전에 서열화되어 A있지만, 어느 불특정 인 3 .

[주]

1 : 엄격한 순서 부분은 인 이진 관계 "<" 집합을 통해 Pasymmetrictransitive모두, 즉 a, bcP: 우리가 가지고

…….. (I). a <b이면 ¬ (b <a) ( asymmetry);
…….. (ii). a <b 및 b <c이면 a <c ( transitivity)입니다.
2 : 순서없는 평가 실행이 겹칠 수 있습니다 .
3 : 불확실한 시퀀스 평가중복 될 수 없지만 둘 중 하나를 먼저 실행할 수 있습니다.


C ++ 11과 관련하여 ‘평가’라는 단어의 의미는 무엇입니까?

C ++ 11에서 일반적으로 표현식 (또는 하위 표현식)의 평가에는 다음이 포함됩니다.

  • 계산 값 (위한 개체의 아이덴티티를 결정하는 단계를 더 포함하는 glvalue 평가 이전의 오브젝트에 할당 된 값을 인출 prvalue 평가 ) 및

  • 부작용의 시작 .

이제 (§1.9 / 14)는 다음과 같이 말합니다.

전체 표현식과 관련된 모든 값 계산 및 부작용 은 평가할 다음 전체 표현식 과 관련된 모든 값 계산 및 부작용 보다 먼저 시퀀싱 됩니다 .

  • 사소한 예 :

    int x;
    x = 10;
    ++x;

    값 계산 및 부작용 ++x은 다음의 값 계산 및 부작용 후에 순서화됩니다.x = 10;


정의되지 않은 행동과 위에 언급 된 것들 사이에는 어떤 관계가 있어야합니다.

예! 권리.

(§1.9 / 15)에서 다음과 같이 언급되었습니다.

언급 된 경우를 제외하고, 각각의 통신 사업자의 개별 식 표현식의 피연산자의 평가는 unsequenced 4 .

예를 들면 다음과 같습니다.

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. +연산자 의 피연산자 평가는 서로에 대해 순서가 없습니다.
  2. 피연산자 <<>>연산자 의 평가는 서로에 대해 순서가 없습니다.

4 : 프로그램 실행 중 두 번 이상 평가되는 표현식에서 하위 표현식에 대해 순서가 지정 되지 않고 불확실하게 시퀀스 된 평가를 다른 평가에서 일관되게 수행 할 필요는 없습니다.

(§1.9 / 15) 연산자 결과의 값 계산 전에 연산자 피연산자의 값 계산이 시퀀싱됩니다.

수단에서 x + y의 계산 값 xy의 값을 계산하기 전에 서열화된다 (x + y).

더 중요한 것은

(§1.9 / 15) 스칼라 객체에 대한 부작용이 다음 중 하나와 관련이없는 경우

(a) 동일한 스칼라 객체에 대한 다른 부작용

또는

(b) 동일한 스칼라 객체의 값을 사용한 값 계산.

동작은 정의되어 있지 않습니다 .

예 :

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

함수가 인라인인지 여부에 관계없이 함수를 호출하면 인수 표현식 또는 호출 된 함수를 지정하는 접미사 표현식과 관련된 모든 값 계산 및 부작용이 본문의 모든 표현식 또는 명령문을 실행하기 전에 순서화됩니다. 함수를 호출했습니다. [ 참고 : 다른 인수 표현식과 관련된 값 계산 및 부작용은 순서가 없습니다 . — 끝 참고 ]

표현식 (5), (7)그리고 (8)정의되지 않은 동작을 호출하지 않습니다. 자세한 설명은 다음 답변을 확인하십시오.


최종 메모 :

게시물에 결함이 있으면 의견을 남겨주세요. 고급 사용자 (rep> 20000)는 오타 및 기타 실수를 수정하기 위해 게시물을 편집하는 것을 망설이지 마십시오.


답변

C ++ 17 ( N4659)에는
보다 엄격한 식 평가 순서를 정의하는 관용적 C ++에 대한 Refining Expression Evaluation Order 제안이 포함되어 있습니다.

특히 다음 문장

8.18 대입 및 복합 대입 연산자 :
….

모든 경우에, 할당은 오른쪽과 왼쪽 피연산자의 값 계산 후와 할당 식의 값 계산 전에 순서화됩니다.
오른쪽 피연산자는 왼쪽 피연산자보다 먼저 시퀀싱됩니다.

다음 설명과 함께

X 와 관련된 모든 값 계산 및 모든 부작용이 식 Y 와 관련된 모든 값 계산 및 모든 부작용 전에 시퀀싱된다면 , 식 X 는 식 Y 전에 시퀀싱된다고한다 .

해당 사건을 포함하여 이전에 정의되지 않은 행동의 여러 사례를 유효하게 만드십시오.

a[++i] = i;

그러나 몇 가지 다른 유사한 사례는 여전히 정의되지 않은 동작으로 이어집니다.

에서 N4140:

i = i++ + 1; // the behavior is undefined

그러나 N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

물론 C ++ 17 호환 컴파일러를 사용한다고해서 반드시 그러한 표현식을 작성해야한다는 의미는 아닙니다.


답변

나는 변화의 근본적인 이유가 있다고 추측하고 있습니다. 오래된 해석을 더 명확하게 만드는 것은 단지 화장품이 아닙니다. 그 이유는 동시성입니다. 지정되지 않은 정교화 순서는 가능한 여러 일련의 순서 중 하나를 선택하는 것입니다. 이는 지정된 순서가 없으면 동시 평가가 가능하기 때문에 순서 전후와는 상당히 다릅니다. 이전 규칙은 그렇지 않습니다. 예를 들면 다음과 같습니다.

f (a,b)

이전에는 a 다음 b 또는 b 다음에 a. 이제 a와 b는 인터리브 된 명령어 또는 다른 코어에서 평가할 수 있습니다.


답변

C99(ISO/IEC 9899:TC3)있는 지금까지 다음 steteents이 evaluaiton의 순서에 관한 만들어진이 토론에없는 것 같다.

[…] 부 표현식 평가 순서와 부작용 발생 순서는 모두 지정되어 있지 않습니다. (섹션 6.5 pp 67)

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


답변