The as-if rule
프로그램의 관찰 가능한 동작을 변경하지 않는 모든 코드 변환을 허용합니다.
목차 |
설명
관찰 가능한 동작 은 프로그램의 다음을 포함합니다:
| (C++11 이전) | |
|
(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
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
|