Namespaces
Variants

Order of evaluation

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

표현식의 어떤 부분이든 평가되는 순서, 함수 인자의 평가 순서를 포함하여 지정되지 않습니다 (아래 나열된 일부 예외 제외). 컴파일러는 피연산자와 다른 하위 표현식을 어떤 순서로든 평가할 수 있으며, 동일한 표현식이 다시 평가될 때 다른 순서를 선택할 수 있습니다.

C++에는 왼쪽에서 오른쪽 또는 오른쪽에서 왼쪽 평가 개념이 존재하지 않습니다. 이는 연산자의 왼쪽에서 오른쪽 및 오른쪽에서 왼쪽 결합성과 혼동해서는 안 됩니다: 표현식 a ( ) + b ( ) + c ( ) operator + 의 왼쪽에서 오른쪽 결합성으로 인해 ( a ( ) + b ( ) ) + c ( ) 로 파싱되지만, 런타임에 c ( ) a ( ) 또는 b ( ) 보다 먼저, 나중에, 또는 사이에 평가될 수 있습니다:

#include <cstdio>
int a() { return std::puts("a"); }
int b() { return std::puts("b"); }
int c() { return std::puts("c"); }
void z(int, int, int) {}
int main()
{
    z(a(), b(), c());       // 출력의 모든 6가지 순열이 허용됨
    return a() + b() + c(); // 출력의 모든 6가지 순열이 허용됨
}

가능한 출력:

b
c
a
c
a 
b

목차

"Sequenced before" 규칙 (since C++11)

표현식 평가

각 표현식의 평가는 다음을 포함합니다:

  • 값 계산(Value computations) : 표현식이 반환하는 값의 계산. 여기에는 객체의 식별자 결정(glvalue 평가, 예: 표현식이 어떤 객체에 대한 참조를 반환하는 경우) 또는 이전에 객체에 할당된 값 읽기(prvalue 평가, 예: 표현식이 숫자나 다른 값을 반환하는 경우)가 포함될 수 있습니다.
  • 부작용(side effects) 의 시작: volatile glvalue로 지정된 객체에 대한 접근(읽기 또는 쓰기), 객체 수정(쓰기), 라이브러리 I/O 함수 호출, 또는 이러한 작업을 수행하는 함수 호출.

순서

Sequenced before 는 동일한 스레드 내에서 평가 A B 사이의 비대칭적, 추이적, 쌍대적 관계입니다.

  • A B 보다 먼저 시퀀싱되면(또는 동등하게 B A 보다 나중에 시퀀싱되면 ), A 의 평가는 B 의 평가가 시작되기 전에 완료됩니다.
  • A B 보다 먼저 시퀀싱되지 않고 B A 보다 먼저 시퀀싱되면, B 의 평가는 A 의 평가가 시작되기 전에 완료됩니다.
  • A B 보다 먼저 시퀀싱되지 않고 B A 보다 먼저 시퀀싱되지 않으면 두 가지 가능성이 존재합니다:
    • A B 의 평가가 비시퀀싱됨 : 어떤 순서로든 수행될 수 있으며 중첩될 수 있습니다(단일 실행 스레드 내에서 컴파일러는 A B 를 구성하는 CPU 명령어들을 인터리브할 수 있습니다).
    • A 와 B의 평가가 불확정적으로 시퀀싱됨 : 어떤 순서로든 수행될 수 있지만 중첩될 수 없습니다: A B 보다 먼저 완료되거나, B A 보다 먼저 완료됩니다. 동일한 표현식이 다음에 평가될 때 순서가 반대일 수 있습니다.

표현식 X 가 표현식 Y 보다 먼저 순서화되었다(sequenced before) 고 말하는 것은, X 와 관련된 모든 값 계산 및 모든 부수 효과가 표현식 Y 와 관련된 모든 값 계산 및 모든 부수 효과보다 먼저 순서화된 경우를 말합니다.

규칙

1) full-expression 은 다음 full-expression보다 먼저 순서가 정해집니다.
2) 모든 operator 의 피연산자에 대한 값 계산(부수 효과는 제외)은 연산자 결과의 값 계산(부수 효과는 제외)보다 먼저 순서가 정해집니다.
3) 함수를 호출할 때 func (함수가 인라인인지 여부와 명시적 함수 호출 구문이 사용되는지 여부와 관계없이), 다음 목록의 각 항목은 다음 항목보다 먼저 순서가 지정됩니다:
  • 모든 인수 표현식과 func 를 지정하는 후위 표현식
(C++26부터)
  • func 의 본문에 있는 모든 표현식 또는 문장
(C++26부터)
4) 내장 후위 증가 및 후위 감소 연산자의 값 계산은 해당 부수 효과보다 먼저 순서가 지정됩니다.
5) 내장 전위 증가 및 전위 감소 연산자의 부수 효과는 해당 값 계산 전에 순서가 정해집니다 (복합 할당으로 정의됨에 따른 암묵적 규칙).
6) 내장 논리 AND 연산자 && , 내장 논리 OR 연산자 || 그리고 내장 쉼표 연산자 , 의 첫 번째(왼쪽) 피연산자는 두 번째(오른쪽) 피연산자보다 먼저 시퀀싱됩니다.
7) 조건부 연산자 ?: 의 첫 번째 피연산자는 두 번째 또는 세 번째 피연산자보다 먼저 시퀀싱됩니다.
8) 내장 assignment 연산자와 모든 내장 compound 할당 연산자의 부작용(좌측 인자의 수정)은 좌측 및 우측 인자의 값 계산(부작용은 제외) 이후에 순서가 지정되고, 할당 표현식의 값 계산(즉, 수정된 객체에 대한 참조를 반환하기 전) 이전에 순서가 지정됩니다.
9) 목록 초기화 에서, 주어진 초기화 절의 모든 값 계산과 부수 효과는 중괄호로 둘러싸인 쉼표로 구분된 초기화자 목록에서 그 뒤에 오는 모든 초기화 절과 관련된 값 계산과 부수 효과보다 먼저 순서가 정해집니다.
10) 함수 호출이 함수 외부의 다른 표현식 평가(아마도 다른 함수 호출)에 대해 sequenced before 또는 sequenced after 관계에 있지 않으면, 해당 평가와 indeterminately sequenced 관계에 있습니다 (프로그램은 함수 호출을 구성하는 CPU 명령어가 다른 표현식 평가(다른 함수 호출 포함)를 구성하는 명령어와 인터리빙되지 않은 것처럼 동작해야 합니다. 함수가 인라인된 경우에도 마찬가지입니다).
규칙 10에는 한 가지 예외가 있습니다: std::execution::par_unseq 실행 정책 하에서 실행되는 표준 라이브러리 알고리즘에 의한 함수 호출은 unsequenced 상태이며 서로 임의로 인터리빙될 수 있습니다. (C++17부터)
11) 할당 함수( operator new )의 호출은 다음에 대해 불확정적으로 순서화됩니다 (C++17까지) 다음보다 순서가 앞섭니다 (C++17부터) new 표현식 에서 생성자 인자의 평가.
12) 함수에서 반환할 때, 함수 호출 평가 결과인 임시 객체의 복사 초기화는 return 의 피연산자 끝에서 발생하는 모든 임시 객체들의 소멸보다 먼저 순서가 지정되며, 이는 다시 return 문을 포함하는 블록의 지역 변수들의 소멸보다 먼저 순서가 지정됩니다.
13) 함수 호출 표현식에서 함수를 지칭하는 표현식은 모든 인수 표현식 및 모든 기본 인수보다 먼저 순서가 정해집니다.
14) 함수 호출에서 모든 매개변수의 초기화에 대한 값 계산 및 부수 효과는 다른 어떤 매개변수의 값 계산 및 부수 효과와 관련하여 비결정적으로 순서가 정해집니다.
15) 모든 오버로드된 연산자는 연산자 표기법을 사용하여 호출될 때 해당 연산자가 오버로드하는 내장 연산자의 순서 규칙을 따릅니다.
16) 첨자 표현식 E1 [ E2 ] 에서, E1 E2 보다 먼저 순서가 정해집니다.
17) 멤버 포인터 표현식 E1. * E2 또는 E1 - > * E2 에서, E1 E2 보다 먼저 순서가 정해집니다 ( E1 의 동적 타입이 E2 이 참조하는 멤버를 포함하지 않는 경우는 제외).
18) 시프트 연산자 표현식 E1 << E2 E1 >> E2 에서, E1 E2 보다 먼저 순서가 정해집니다.
19) 모든 단순 대입 표현식 E1 = E2 및 모든 복합 대입 표현식 E1 @ = E2 에서, E2 E1 보다 먼저 순서가 정해집니다.
20) 괄호로 묶은 초기화자에서 쉼표로 구분된 표현식 목록의 모든 표현식은 함수 호출과 같이 평가됩니다(비결정적 순서).
(C++17부터)

정의되지 않은 동작

동작은 다음과 같은 경우에 undefined 입니다:

1) 동일한 메모리 위치 에 대한 부수 효과는 다른 부수 효과와 비순서 관계에 있습니다:
i = ++i + 2;       // well-defined
i = i++ + 2;       // undefined behavior until C++17
f(i = -2, i = -2); // undefined behavior until C++17
f(++i, ++i);       // undefined behavior until C++17, unspecified after C++17
i = ++i + i++;     // undefined behavior
2) 메모리 위치에 대한 부수 효과(side effect)는 동일한 메모리 위치에 있는 임의의 객체 값을 사용하는 값 계산(value computation)과 비순서 관계(unsequenced)에 있습니다:
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
3) 메모리 위치에서 객체의 lifetime 을 시작하거나 종료하는 것은 다음 작업들에 대해 순서가 지정되지 않습니다:
  • 동일한 메모리 위치에 대한 부작용(side effect)
  • 동일한 메모리 위치에 있는 임의의 객체 값을 사용한 값 계산(value computation)
  • 해당 메모리 위치와 겹치는 저장 공간을 차지하는 객체의 lifetime 시작 또는 종료
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

시퀀스 포인트 규칙 (C++11까지)

C++11 이전 정의

표현식의 평가는 부수 효과를 발생시킬 수 있으며, 이는 다음과 같습니다: volatile 좌측값으로 지정된 객체에 접근하기, 객체 수정하기, 라이브러리 I/O 함수 호출하기, 또는 이러한 연산 중 어떤 것을 수행하는 함수 호출하기.

시퀀스 포인트 는 실행 시퀀스 상의 한 지점으로, 시퀀스 내 이전 평가들의 모든 부수 효과가 완료되고, 이후 평가들의 부수 효과는 아직 시작되지 않은 상태를 말합니다.

C++11 이전 규칙

1) full-expression 의 끝에는 시퀀스 포인트가 존재합니다 (일반적으로 세미콜론에서).
2) 함수를 호출할 때 (함수가 인라인인지 여부와 함수 호출 구문이 사용되었는지 여부에 관계없이), 모든 함수 인자(있는 경우)의 평가 후에는 함수 본문 내의 어떤 표현식이나 문장이 실행되기 전에 시퀀스 포인트가 존재합니다.
3) 함수에서 반환할 때, 함수 호출 결과의 복사 초기화 이후와 return 표현식 끝에서 모든 임시 객체의 파괴 전 사이에 시퀀스 포인트가 존재합니다.
4) 함수의 반환값 복사 후, 함수 외부의 어떤 표현식이 실행되기 전에 시퀀스 포인트가 존재합니다.
5) 함수의 실행이 시작되면, 호출된 함수의 실행이 완료될 때까지 호출 함수의 어떤 표현식도 평가되지 않습니다 (함수들은 인터리브될 수 없습니다).
6) 다음 네 가지 표현식 각각의 평가에서, 내장(오버로드되지 않은) 연산자를 사용할 경우, 표현식 a 의 평가 이후에 시퀀스 포인트가 존재합니다.
a && b
a || b
a ? b : c
a , b

C++11 이전의 정의되지 않은 동작

동작은 다음과 같은 경우에 undefined 입니다:

1) 이전 및 다음 시퀀스 포인트 사이에서 메모리 위치의 객체 값이 표현식 평가에 의해 두 번 이상 수정되는 경우:
i = ++i + i++;     // undefined behavior
i = i++ + 1;       // undefined behavior
i = ++i + 1;       // undefined behavior
++ ++i;            // undefined behavior
f(++i, ++i);       // undefined behavior
f(i = -1, i = -1); // undefined behavior
2) 이전 및 다음 시퀀스 포인트 사이에서, 표현식 평가에 의해 값이 수정되는 객체의 이전 값이 저장될 값을 결정하는 것 이외의 방식으로 접근되는 경우:
cout << i << i++; // undefined behavior
a[i] = i++;       // undefined behavior

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 1885 C++11 함수 반환 시 자동 변수의 소멸 순서가 명시적이지 않았음 순서 규칙 추가됨
CWG 1949 C++11 C++ 표준에서 "sequenced after"가 사용되었지만 정의되지 않았음 "sequenced before"의 역으로 정의됨
CWG 1953 C++11 메모리 위치와 관련된 부수 효과 및 값 계산이 동일 메모리 위치에서
객체의 수명 시작/종료와 비순서 관계를 가질 수 있었음
이 경우 동작이
정의되지 않음
CWG 2146 C++98 정의되지 않은 동작을 포함하는 경우들이 비트 필드를 고려하지 않았음 고려됨

참고문헌

  • C++23 표준 (ISO/IEC 14882:2024):
  • 6.9.1 프로그램 실행 [intro.execution]
  • 7.6.1.6 증가 및 감소 연산자 [expr.post.incr]
  • 7.6.2.8 new 연산자 [expr.new]
  • 7.6.14 논리 AND 연산자 [expr.log.and]
  • 7.6.15 논리 OR 연산자 [expr.log.or]
  • 7.6.16 조건 연산자 [expr.cond]
  • 7.6.19 대입 및 복합 대입 연산자 [expr.ass]
  • 7.6.20 쉼표 연산자 [expr.comma]
  • 9.4.5 목록 초기화 [dcl.init.list]
  • C++20 표준(ISO/IEC 14882:2020):
  • 6.9.1 프로그램 실행 [intro.execution]
  • 7.6.1.5 증가 및 감소 연산자 [expr.post.incr]
  • 7.6.2.7 new 연산자 [expr.new]
  • 7.6.14 논리 AND 연산자 [expr.log.and]
  • 7.6.15 논리 OR 연산자 [expr.log.or]
  • 7.6.16 조건 연산자 [expr.cond]
  • 7.6.19 대입 및 복합 대입 연산자 [expr.ass]
  • 7.6.20 쉼표 연산자 [expr.comma]
  • 9.4.4 목록 초기화 [dcl.init.list]
  • C++17 표준(ISO/IEC 14882:2017):
  • 4.6 프로그램 실행 [intro.execution]
  • 8.2.6 증가 및 감소 연산자 [expr.post.incr]
  • 8.3.4 new 연산자 [expr.new]
  • 8.14 논리 AND 연산자 [expr.log.and]
  • 8.15 논리 OR 연산자 [expr.log.or]
  • 8.16 조건 연산자 [expr.cond]
  • 8.18 대입 및 복합 대입 연산자 [expr.ass]
  • 8.19 쉼표 연산자 [expr.comma]
  • 11.6.4 목록 초기화 [dcl.init.list]
  • C++14 표준(ISO/IEC 14882:2014):
  • 1.9 프로그램 실행 [intro.execution]
  • 5.2.6 증가 및 감소 연산자 [expr.post.incr]
  • 5.3.4 new 연산자 [expr.new]
  • 5.14 논리 AND 연산자 [expr.log.and]
  • 5.15 논리 OR 연산자 [expr.log.or]
  • 5.16 조건 연산자 [expr.cond]
  • 5.17 대입 및 복합 대입 연산자 [expr.ass]
  • 5.18 쉼표 연산자 [expr.comma]
  • 8.5.4 목록 초기화 [dcl.init.list]
  • C++11 표준 (ISO/IEC 14882:2011):
  • 1.9 프로그램 실행 [intro.execution]
  • 5.2.6 증가 및 감소 연산자 [expr.post.incr]
  • 5.3.4 new 연산자 [expr.new]
  • 5.14 논리 AND 연산자 [expr.log.and]
  • 5.15 논리 OR 연산자 [expr.log.or]
  • 5.16 조건 연산자 [expr.cond]
  • 5.17 대입 및 복합 대입 연산자 [expr.ass]
  • 5.18 쉼표 연산자 [expr.comma]
  • 8.5.4 목록 초기화 [dcl.init.list]

참고 항목

C 문서 참조: 평가 순서