다음을 통해 공유


CRT 초기화

이 문서에서는 CRT가 네이티브 코드에서 전역 상태를 초기화하는 방법을 설명합니다.

기본적으로 링커는 자체 시작 코드를 제공하는 CRT 라이브러리를 포함합니다. 이 시작 코드는 CRT 라이브러리를 초기화하고 전역 이니셜라이저를 호출한 다음 콘솔 애플리케이션에 대해 사용자 제공 main 함수를 호출합니다.

권장되지는 않지만 Microsoft 관련 링커 동작을 활용하여 고유한 전역 이니셜라이저를 특정 순서로 삽입할 수 있습니다. 이 코드는 이식 가능하지 않으며 몇 가지 중요한 주의 사항이 함께 제공됩니다.

전역 개체 초기화

다음 C++ 코드를 고려합니다(C는 상수 식에서 함수 호출을 허용하지 않으므로 이 코드를 허용하지 않음).

int func(void)
{
    return 3;
}

int gi = func();

int main()
{
    return gi;
}

C/C++ 표준에 따라 func()을 실행하기 전에 main()를 호출해야 합니다. 하지만 누가 호출할까요?

호출자를 확인하는 한 가지 방법은 중단점을 func()설정하고, 애플리케이션을 디버그하고, 스택을 검사하는 것입니다. CRT 소스 코드가 Visual Studio에 포함되어 있기 때문에 가능합니다.

스택에서 함수를 찾아보면 CRT가 함수 포인터 목록을 호출하는 것을 볼 수 있습니다. 이러한 함수는 클래스 인스턴스의 func()생성자 또는 유사합니다.

CRT는 Microsoft C++ 컴파일러에서 함수 포인터 목록을 가져옵니다. 컴파일러에 전역 이니셜라이저가 표시되면 섹션 이름 및 XCU 그룹 이름이 있는 CRT 섹션에서 동적 이니셜라이저 .CRT$XCU 를 생성합니다. 동적 이니셜라이저 목록을 얻으려면 명령을 dumpbin /all main.obj실행한 다음 섹션을 검색합니다 .CRT$XCU . 이 명령은 C 파일이 아닌 C++ 파일로 컴파일된 경우에만 적용됩니다 main.cpp . 이 예제와 유사해야 합니다.

SECTION HEADER #6
.CRT$XCU name
       0 physical address
       0 virtual address
       4 size of raw data
     1F2 file pointer to raw data (000001F2 to 000001F5)
     1F6 file pointer to relocation table
       0 file pointer to line numbers
       1 number of relocations
       0 number of line numbers
40300040 flags
         Initialized Data
         4 byte align
         Read Only

RAW DATA #6
  00000000: 00 00 00 00                                      ....

RELOCATIONS #6
                                               Symbol    Symbol
Offset    Type              Applied To         Index     Name
--------  ----------------  -----------------  --------  -------
00000000  DIR32             00000000           C         ??__Egi@@YAXXZ (void __cdecl `dynamic initializer for 'gi''(void))

CRT는 두 포인터를 정의합니다.

  • .CRT$XCA__xc_a
  • .CRT$XCZ__xc_z

두 그룹에는 다음을 제외한 __xc_a __xc_z다른 기호가 정의되어 있지 않습니다.

이제 링커가 다양한 .CRT 하위 섹션(부분 뒤 $)을 읽을 때 한 섹션에 결합하고 사전순으로 정렬합니다. 즉, Microsoft C++ 컴파일러가 배치하는 사용자 정의 전역 이니셜라이저는 .CRT$XCU항상 뒤와 .CRT$XCA 그 앞에 .CRT$XCZ옵니다.

섹션은 다음 예제와 유사합니다.

.CRT$XCA
            __xc_a
.CRT$XCU
            Pointer to Global Initializer 1
            Pointer to Global Initializer 2
.CRT$XCZ
            __xc_z

CRT 라이브러리는 이미지를 로드한 후 메모리에 배치되는 방식 때문에 전역 이니셜라이저 목록의 시작과 끝을 둘 다 __xc_a __xc_z 사용하고 결정합니다.

초기화를 위한 링커 기능

C++ 표준은 사용자가 제공한 전역 이니셜라이저에 대해 번역 단위 간에 상대적 순서를 지정하는 규칙적인 방법을 제공하지 않습니다. 그러나 Microsoft 링커는 하위 섹션을 .CRT 사전순으로 주문하므로 이 순서를 활용하여 초기화 순서를 지정할 수 있습니다. 이 Microsoft 관련 기술은 권장하지 않으며 향후 릴리스에서 중단될 수 있습니다. 진단하기 어려운 방법으로 끊어진 코드를 만들지 못하도록 하기 위해서만 문서화했습니다.

Visual Studio 2019 버전 16.11부터 코드의 문제를 방지하기 위해 C5247C5248이라는 두 가지 새로운 경고를 기본 경고로 추가했습니다. 사용자 고유의 이니셜라이저를 만들 때 문제를 감지하려면 이러한 경고를 사용하도록 설정합니다.

사용하지 않는 예약된 섹션 이름에 이니셜라이저를 추가하여 컴파일러에서 생성된 동적 이니셜라이저에 대한 특정 상대 순서로 만들 수 있습니다.

#pragma section(".CRT$XCT", read)
// 'i1' is guaranteed to be called before any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCT")) type i1 = f;

#pragma section(".CRT$XCV", read)
// 'i2' is guaranteed to be called after any compiler generated C++ dynamic initializer
__declspec(allocate(".CRT$XCV")) type i2 = f;

이름은 .CRT$XCT .CRT$XCV 현재 컴파일러 또는 CRT 라이브러리에서 사용되지 않지만 나중에 사용하지 않을 것이라는 보장은 없습니다. 또한 컴파일러에서 변수를 최적화할 수 있습니다. 이 기술을 채택하기 전에 잠재적인 엔지니어링, 유지 관리 및 이식성 문제를 고려합니다.

참고 항목

_initterm, _initterm_e
C 런타임(CRT) 및 C++ STL(표준 라이브러리) .lib 파일