Phases of translation
C++ 소스 파일은 컴파일러에 의해 처리되어 C++ 프로그램을 생성합니다.
목차 |
번역 과정
C++ 프로그램의 텍스트는 소스 파일 이라는 단위로 유지됩니다.
C++ 소스 파일은 다음 단계를 거쳐 번역(translation) 되어 번역 단위(translation unit) 가 됩니다:
- 각 소스 파일을 문자 시퀀스에 매핑합니다.
- 각 문자 시퀀스를 공백으로 구분된 전처리 토큰 시퀀스로 변환합니다.
- 각 전처리 토큰을 토큰으로 변환하여 토큰 시퀀스를 형성합니다.
- 각 토큰 시퀀스를 번역 단위로 변환합니다.
C++ 프로그램은 번역된 번역 단위들로 구성될 수 있습니다. 번역된 번역 단위들과 인스턴스화된 단위들(인스턴스화된 단위들은 아래 8단계에서 설명됨)은 개별적으로 저장되거나 라이브러리에 저장될 수 있습니다. 여러 번역 단위들은 (예를 들어) 외부 링크를 가진 심볼들이나 데이터 파일들을 통해 서로 통신합니다. 번역 단위들은 별도로 번역된 후 나중에 링크되어 실행 가능한 프로그램을 생성할 수 있습니다.
위의 과정은 9개의 translation phases 로 구성될 수 있습니다.
전처리 토큰
전처리 토큰 은 번역 단계 3부터 6까지 언어의 최소 어휘 요소입니다.
전처리 토큰의 범주는 다음과 같습니다:
- 헤더 이름 (예: < iostream > 또는 "myfile.h" )
|
(C++20부터) |
- 식별자
- 전처리기 숫자 (아래 참조)
- 문자 리터럴 , 포함하여 사용자 정의 문자 리터럴 (C++11부터)
- 문자열 리터럴 , 포함하여 사용자 정의 문자열 리터럴 (C++11부터)
- 연산자와 구분자 , 포함하여 대체 토큰
- 다른 범주에 속하지 않는 개별 공백이 아닌 문자들
-
프로그램은 이 범주에 해당하는 문자가 다음 중 하나일 경우 형식이 잘못되었습니다:
- 아포스트로피 ( ' , U+0027),
- 따옴표 ( " , U+0022), 또는
- 기본 문자 집합 에 포함되지 않은 문자인 경우.
전처리 숫자
전처리 숫자의 전처리 토큰 집합은 정수 리터럴 과 부동소수점 리터럴 의 토큰 집합 합집합의 상위 집합입니다:
.
(선택 사항)
digit
pp-continue-seq
(선택 사항)
|
|||||||||
| digit | - | 0-9 숫자 중 하나 |
| pp-continue-seq | - | pp-continue 들의 연속 |
각 pp-continue 는 다음 중 하나입니다:
| identifier-continue | (1) | ||||||||
| exp-char sign-char | (2) | ||||||||
.
|
(3) | ||||||||
’
digit
|
(4) | (C++14 이후) | |||||||
’
nondigit
|
(5) | (C++14 이후) | |||||||
| identifier-continue | - | 유효한 식별자 의 첫 번째가 아닌 모든 문자 |
| exp-char | - |
다음 중 하나
P
,
p
,
(C++11부터)
E
와
e
|
| sign-char | - |
다음 중 하나
+
와
-
|
| digit | - | 숫자 0-9 중 하나 |
| nondigit | - | 라틴 문자 A/a-Z/z 및 밑줄 중 하나 |
전처리 숫자는 타입이나 값을 가지지 않으며, 정수/부동소수점 리터럴 토큰으로의 성공적인 변환 후에 둘 다 획득합니다.
공백
공백 은 주석 , 공백 문자, 또는 둘 다로 구성됩니다.
다음 문자들은 공백 문자입니다:
- 문자 탭 (U+0009)
- 줄 바꿈 / 새 줄 문자 (U+000A)
- 줄 탭 (U+000B)
- 폼 피드 (U+000C)
- 공백 (U+0020)
공백은 일반적으로 전처리 토큰을 분리하는 데 사용되며, 다음과 같은 예외가 있습니다:
- 헤더 이름, 문자 리터럴 및 문자열 리터럴 내에서는 구분자가 아닙니다.
- 개행 문자를 포함하는 공백으로 구분된 전처리 토큰은 preprocessing directives 를 형성할 수 없습니다.
#include "my header" // OK, 공백을 포함하는 헤더 이름 사용 #include/*hello*/<iostream> // OK, 주석을 공백으로 사용 #include <iostream> // Error: #include는 여러 줄에 걸쳐 있을 수 없음 "str ing" // OK, 단일 전처리 토큰 (문자열 리터럴) ' ' // OK, 단일 전처리 토큰 (문자 리터럴)
최대 먼치
최대 먼치(maximal munch)는 3단계에서 소스 파일을 전처리 토큰으로 분해할 때 사용되는 규칙입니다.
입력이 주어진 문자까지 전처리 토큰으로 파싱된 경우(그렇지 않으면 다음 전처리 토큰이 파싱되지 않아 파싱 순서가 고유해짐), 다음 전처리 토큰은 일반적으로 후속 분석이 실패하더라도 전처리 토큰을 구성할 수 있는 가장 긴 문자 시퀀스로 간주됩니다. 이는 일반적으로 최대한 많이 집어삼키기(maximal munch) 로 알려져 있습니다.
int foo = 1; int bar = 0xE+foo; // 오류: 유효하지 않은 전처리 숫자 0xE+foo int baz = 0xE + foo; // 정상
다시 말해, 최대한 많이 씹기 규칙은 multi-character operators and punctuators 를 우선시합니다:
int foo = 1; int bar = 2; int num1 = foo+++++bar; // 오류: "foo++ ++ +baz"로 처리됨, "foo++ + ++baz"가 아님 int num2 = -----foo; // 오류: "-- -- -foo"로 처리됨, "- -- --foo"가 아님
최대 먼치 규칙에는 다음과 같은 예외가 있습니다:
- 헤더 이름 전처리 토큰은 다음 경우에만 형성됩니다:
-
- #include 지시문에서 include 전처리 토큰 뒤에 나오는
|
(C++17부터) |
|
(C++20부터) |
std::vector<int> x; // OK, "int"는 헤더 이름이 아님
- 다음 세 문자가 < :: 이고, 이후 문자가 : 도 아니고 > 도 아닌 경우, < 는 alternative token < : 의 첫 번째 문자로 처리되지 않고 단독 전처리 토큰으로 처리됩니다.
struct Foo { static const int v = 1; }; std::vector<::Foo> x; // OK, <:가 [에 대한 대체 토큰으로 해석되지 않음 extern int y<::>; // OK, "extern int y[];"와 동일 int z<:::Foo::value:>; // OK, "int z[::Foo::value];"와 동일
template<int i> class X { /* ... */ }; template<class T> class Y { /* ... */ }; Y<X<1>> x3; // OK, "Y<X<1> >" 타입의 변수 "x3"을 선언 Y<X<6>>1>> x4; // 구문 오류 Y<X<(6>>1)>> x5; // OK
#define R "x" const char* s = R"y"; // 잘못된 원시 문자열 리터럴, "x" "y"가 아님 const char* s2 = R"(a)" "b)"; // 원시 문자열 리터럴 뒤에 일반 문자열 리터럴이 옴 |
(C++11부터) |
토큰
토큰은 번역 단계 7에서 언어의 최소 어휘 요소입니다.
토큰의 범주는 다음과 같습니다:
번역 단계
번역은 1단계부터 9단계까지의 순서로 as if 수행됩니다. 실제로는 서로 다른 단계들이 함께 결합될 수 있지만, 구현체들은 이러한 개별 단계들이 발생하는 것처럼 동작합니다.
Phase 1: 소스 문자 매핑
|
1)
소스 코드 파일의 개별 바이트들은 (구현 정의 방식으로)
기본 소스 문자 집합
의 문자들에 매핑됩니다. 특히, 운영체제에 종속적인 줄 끝 표시자는 개행 문자로 대체됩니다.
2)
허용되는 소스 파일 문자 집합은 구현 정의됩니다
(C++11부터)
.
기본 소스 문자 집합
의 문자로 매핑될 수 없는 소스 파일 문자는 해당 문자의
유니버설 문자 이름
(
\u
또는
\U
로 이스케이프됨)이나 동등하게 처리되는 구현 정의 형태로 대체됩니다.
|
(C++23까지) | ||
|
UTF-8 코드 단위 시퀀스인 입력 파일(UTF-8 파일)은 지원이 보장됩니다. 지원되는 다른 종류의 입력 파일 집합은 구현 정의됩니다. 집합이 비어있지 않은 경우, 입력 파일의 종류는 입력 파일을 그 내용과 무관하게 UTF-8 파일로 지정하는 수단을 포함하는 구현 정의 방식으로 결정됩니다(바이트 순서 표시를 인식하는 것만으로는 충분하지 않음).
|
(C++23부터) |
Phase 2: 라인 스플라이싱
Phase 3: 렉싱
// The following #include directive can de decomposed into 5 preprocessing tokens: // punctuators (#, < and >) // │ // ┌────────┼────────┐ // │ │ │ #include <iostream> // │ │ // │ └── header name (iostream) // │ // └─────────── identifier (include)
// Error: partial string literal "abc
// Error: partial comment /* comment
|
소스 파일의 문자들이 다음 전처리 토큰을 형성하기 위해 소비될 때(즉, 주석이나 다른 형태의 공백의 일부로 소비되지 않을 때), 유니버설 문자 이름이 인식되고
변환 문자 집합
의 지정된 요소로 대체됩니다. 단, 다음 전처리 토큰들 중 하나에서 문자 시퀀스를 매칭하는 경우는 예외입니다:
|
(C++23부터) |
| (C++11부터) |
- 각 주석은 하나의 공백 문자로 대체됩니다.
- 줄바꿈 문자는 유지됩니다.
- 줄바꿈 이외의 공백 문자로 이루어진 각 비어 있지 않은 시퀀스가 유지되거나 하나의 공백 문자로 대체되는지는 명시되지 않습니다.
Phase 4: 전처리
Phase 5: 공통 문자열 리터럴 인코딩 결정
|
1)
문자 리터럴
과
문자열 리터럴
의 모든 문자는 소스 문자 집합에서
인코딩
으로 변환됩니다
(
기본 문자 집합
의 96개 문자가 단일 바이트 표현을 가지는 경우 UTF-8과 같은 멀티바이트 문자 인코딩일 수 있습니다).
2)
이스케이프 시퀀스
와 유니버설 문자 이름은 문자 리터럴과 비-원시 문자열 리터럴에서 확장되어 리터럴 인코딩으로 변환됩니다.
유니버설 문자 이름으로 지정된 문자가 해당 리터럴 인코딩에서 단일 코드 포인트로 인코딩될 수 없는 경우, 결과는 구현에 따라 정의되지만 널 (와이드) 문자가 아니라는 것이 보장됩니다. |
(C++23까지) |
|
두 개 이상의 인접한 문자열 리터럴 토큰 시퀀스에 대해, 공통 인코딩 접두사는 여기 에 설명된 대로 결정됩니다. 각 해당 문자열 리터럴 토큰은 그 공통 인코딩 접두사를 가지는 것으로 간주됩니다. (문자 변환은 3단계로 이동됨) |
(C++23부터) |
단계 6: 문자열 리터럴 연결하기
인접한 string literals 은 연결됩니다.
단계 7: 컴파일
컴파일이 수행됩니다: 각 전처리 토큰은 token 으로 변환됩니다. 토큰들은 구문 및 의미적으로 분석되고 translation unit 으로 번역됩니다.
단계 8: 템플릿 인스턴스화
각 번역 단위는 명시적 인스턴스화 로 요청된 것들을 포함하여 필요한 템플릿 인스턴스화 목록을 생성하기 위해 검사됩니다. 템플릿의 정의들이 위치를 찾아내고, 필요한 인스턴스화들이 수행되어 인스턴스화 단위 를 생성합니다.
단계 9: 링킹
외부 참조를 충족시키기 위해 필요한 번역 단위, 인스턴스화 단위, 그리고 라이브러리 구성 요소들은 실행 환경에서 실행에 필요한 정보를 포함하는 프로그램 이미지로 수집됩니다.
참고 사항
소스 파일, 번역 단위 및 번역된 번역 단위는 반드시 파일로 저장될 필요가 없으며, 이러한 개체들과 외부 표현 간에 일대일 대응이 반드시 존재할 필요도 없습니다. 이 설명은 개념적일 뿐이며, 특정 구현을 명시하지 않습니다.
|
5단계에서 수행되는 변환은 일부 구현에서 명령줄 옵션으로 제어할 수 있습니다: gcc와 clang은 - finput - charset 을 사용하여 소스 문자 집합의 인코딩을 지정하고, - fexec - charset 과 - fwide - exec - charset 을 사용하여 각각 일반 리터럴 인코딩과 와이드 리터럴 인코딩을 지정합니다. 반면 Visual Studio 2015 업데이트 2 및 이후 버전은 / source - charset 과 / execution - charset 을 사용하여 각각 소스 문자 집합과 리터럴 인코딩을 지정합니다. |
(C++23까지) |
일부 컴파일러는 인스턴스화 유닛(일명 template repositories 또는 template registries )을 구현하지 않고 단순히 7단계에서 각 템플릿 인스턴스화를 컴파일하여, 암시적 또는 명시적으로 요청된 객체 파일에 코드를 저장한 다음, 링커가 9단계에서 이러한 컴파일된 인스턴스화들을 하나로 통합합니다.
결함 보고서
다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 787 | C++98 |
비어 있지 않은 소스 파일이 2단계 끝에서
개행 문자로 끝나지 않을 경우 동작이 정의되지 않음 |
이 경우 종료 개행 문자를
추가함 |
| CWG 1104 | C++98 |
대체 토큰
<
:
때문에
std::
vector
<
::
std::
string
>
가 std:: vector [ : std:: string > 로 처리됨 |
이 경우를 처리하기 위한 추가적인
렉싱 규칙을 추가함 |
| CWG 1775 | C++11 |
2단계에서 원시 문자열 리터럴 내부에서
유니버설 문자 이름을 형성하면 정의되지 않은 동작이 발생함 |
올바르게 정의됨 |
| CWG 2747 | C++98 | 2단계에서 스플라이싱 후 파일 끝 스플라이스를 확인했으나 이는 불필요함 | 확인을 제거함 |
| P2621R3 | C++98 |
유니버설 문자 이름을 라인 스플라이싱이나
토큰 연결로 형성하는 것이 허용되지 않았음 |
허용됨 |
참고문헌
- C++23 표준 (ISO/IEC 14882:2024):
-
- 5.2 번역 단계 [lex.phases]
- C++20 표준(ISO/IEC 14882:2020):
-
- 5.2 변환 단계 [lex.phases]
- C++17 표준 (ISO/IEC 14882:2017):
-
- 5.2 변환 단계 [lex.phases]
- C++14 표준 (ISO/IEC 14882:2014):
-
- 2.2 번역 단계 [lex.phases]
- C++11 표준 (ISO/IEC 14882:2011):
-
- 2.2 번역 단계 [lex.phases]
- C++03 표준 (ISO/IEC 14882:2003):
-
- 2.1 번역 단계 [lex.phases]
- C++98 표준 (ISO/IEC 14882:1998):
-
- 2.1 번역 단계 [lex.phases]
참고 항목
|
C 문서
참조:
번역 단계
|