Namespaces
Variants

The as-if rule

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

프로그램의 관찰 가능한 동작을 변경하지 않는 모든 코드 변환을 허용합니다.

목차

설명

관찰 가능한 동작 은 프로그램의 다음을 포함합니다:

  • 모든 시퀀스 포인트 에서, 모든 volatile 객체의 값은 안정적입니다 (이전 평가는 완료되었고, 새로운 평가는 시작되지 않음).
(C++11 이전)
  • volatile 객체에 대한 접근(읽기 및 쓰기)은 발생하는 표현식의 의미론에 따라 엄격하게 수행됩니다. 특히, 동일한 스레드 내의 다른 volatile 접근과 관련하여 재배열되지 않습니다 .
(C++11 이후)
  • 프로그램 종료 시, 파일에 기록된 데이터는 프로그램이 작성된 대로 실행된 것과 정확히 동일합니다.
(until C++26)
  • 호스트 환경에 전달된 데이터는 파일에 기록됩니다.
(since C++26)
  • 대화형 장치로 전송되는 프롬프트 텍스트는 프로그램이 입력을 대기하기 전에 표시됩니다.
  • ISO C 프라그마 #pragma STDC FENV_ACCESS 이 지원되고 ON 으로 설정된 경우, 부동 소수점 환경 (부동 소수점 예외 및 반올림 모드)에 대한 변경 사항은 다음과 같은 예외를 제외하고 작성된 대로 실행되는 것처럼 부동 소수점 산술 연산자 및 함수 호출에서 관찰됨이 보장됩니다:
    • 캐스트 및 할당 이외의 모든 부동 소수점 표현식의 결과는 표현식의 타입과 다른 부동 소수점 타입의 범위와 정밀도를 가질 수 있음 ( FLT_EVAL_METHOD 참조),
    • 위 사항에도 불구하고, 모든 부동 소수점 표현식의 중간 결과는 무한 범위와 정밀도로 계산된 것처럼 계산될 수 있음 ( #pragma STDC FP_CONTRACT OFF 가 아닌 경우).

C++ 컴파일러는 동일한 입력이 주어졌을 때 프로그램의 관찰 가능한 동작이 해당 입력에 대응하는 가능한 관찰 가능한 동작 중 하나라면 프로그램에 어떠한 변경도 수행할 수 있습니다.

그러나 특정 입력이 undefined behavior 를 초래할 경우, 컴파일러는 관찰 가능한 동작의 어떤 연산이 가능한 undefined operation보다 먼저 발생하더라도 해당 입력으로 프로그램의 어떤 관찰 가능한 동작도 보장할 수 없습니다.

(until C++26)

프로그램은 observable checkpoints  를 포함할 수 있습니다.

모든 undefined operation U 에 대해 OP CP 보다 먼저 발생하고 CP U 보다 먼저 발생하는 observable checkpoint CP 가 존재하면 연산 OP undefined-free 입니다. 주어진 입력에 대한 프로그램의 defined prefix 는 모든 undefined-free 연산으로 구성됩니다.

C++ 컴파일러는 동일한 입력이 주어졌을 때 프로그램의 defined prefix의 관찰 가능한 동작이 해당 defined prefix에 대응하는 가능한 관찰 가능한 동작 중 하나라면 프로그램에 어떠한 변경도 수행할 수 있습니다.

특정 입력이 undefined behavior 를 초래할 경우, 컴파일러는 defined prefix에 속하지 않는 해당 입력으로 프로그램의 어떤 관찰 가능한 동작도 보장할 수 없습니다.

(since C++26)

참고 사항

컴파일러가 (일반적으로) 외부 라이브러리의 코드를 분석하여 I/O 또는 volatile 액세스를 수행하는지 여부를 판단할 수 없기 때문에, 서드파티 라이브러리 호출도 최적화의 영향을 받지 않습니다. 그러나 표준 라이브러리 호출은 최적화 과정에서 다른 호출로 대체되거나, 제거되거나, 프로그램에 추가될 수 있습니다. 정적으로 링크된 서드파티 라이브러리 코드는 링크 타임 최적화의 대상이 될 수 있습니다.

정의되지 않은 동작을 가진 프로그램들은 종종 다른 최적화 설정으로 재컴파일될 때 관찰 가능한 동작을 변경합니다. 예를 들어, 부호 있는 정수 오버플로 검사가 해당 오버플로의 결과에 의존하는 경우, 예를 들어 if ( n + 1 < n ) abort ( ) ; , 일부 컴파일러에 의해 완전히 제거됩니다 왜냐하면 부호 있는 오버플로는 정의되지 않은 동작 이며 최적화기는 이것이 절대 발생하지 않는다고 가정하고 검사를 중복으로 판단할 수 있기 때문입니다.

복사 생략 은 as-if 규칙의 예외입니다: 컴파일러는 이동 및 복사 생성자에 대한 호출과 임시 객체의 소멸자에 대한 일치하는 호출을 해당 호출들이 관찰 가능한 부수 효과를 가지더라도 제거할 수 있습니다.

new expression 는 as-if 규칙에 대한 또 다른 예외를 가집니다: 컴파일러는 사용자 정의 대체 함수가 제공되고 관찰 가능한 부작용이 있더라도 replaceable allocation functions 호출을 제거할 수 있습니다.

(since C++14)

부동 소수점 예외의 개수와 순서는 다음 부동 소수점 연산에서 관찰되는 상태가 최적화가 이루어지지 않은 것처럼 보이는 한 최적화에 의해 변경될 수 있습니다:

#pragma STDC FENV_ACCESS ON
for (i = 0; i < n; ++i)
    x + 1; // x + 1은 데드 코드이지만 FP 예외를 발생시킬 수 있음
           // (최적화기가 달리 증명할 수 없는 경우). 그러나 이를 n번 실행하면
           // 동일한 예외가 반복해서 발생함. 따라서 다음과 같이 최적화될 수 있음:
if (0 < n)
    x + 1;

예제

int& preinc(int& n) { return ++n; }
int add(int n, int m) { return n + m; }
// 상수 폴딩을 방지하기 위한 volatile 입력
volatile int input = 7;
// 결과를 가시적인 부수 효과로 만들기 위한 volatile 출력
volatile int result;
int main()
{
    int n = input;
// 내장 연산자를 사용하면 정의되지 않은 동작을 유발함
//  int m = ++n + ++n;
// 하지만 함수를 사용하면 함수들이 겹치지 않은 것처럼 코드가 실행됨
    int m = add(preinc(n), preinc(n));
    result = m;
}

출력:

# GCC 컴파일러에 의해 생성된 main() 함수의 전체 코드
# x86 (Intel) 플랫폼:
        movl    input(%rip), %eax   # eax = input
        leal    3(%rax,%rax), %eax  # eax = 3 + eax + eax
        movl    %eax, result(%rip)  # result = eax
        xorl    %eax, %eax          # eax = 0 (main()의 반환 값)
        ret
# PowerPC (IBM) 플랫폼:
        lwz 9,LC..1(2)
        li 3,0          # r3 = 0 (main()의 반환 값)
        lwz 11,0(9)     # r11 = input;
        slwi 11,11,1    # r11 = r11 << 1;
        addi 0,11,3     # r0 = r11 + 3;
        stw 0,4(9)      # result = r0;
        blr
# Sparc (Sun) 플랫폼:
        sethi   %hi(result), %g2
        sethi   %hi(input), %g1
        mov     0, %o0                 # o0 = 0 (main()의 반환 값)
        ld      [%g1+%lo(input)], %g1  # g1 = input
        add     %g1, %g1, %g1          # g1 = g1 + g1
        add     %g1, 3, %g1            # g1 = 3 + g1
        st      %g1, [%g2+%lo(result)] # result = g1
        jmp     %o7+8
        nop
# 모든 경우에서 preinc()의 부수 효과는 제거되었으며,
# 전체 main() 함수는 result = 2 * input + 3;과 동등하게 축소됨

참고 항목

C 문서 for as-if rule