프로그래밍/C++ & MFC

CRT (C Run-time Libraries)

단칸이 2021. 7. 12. 23:34

C Run-time Libraries 란?

프로그래밍 언어는 구문이나 예약어 같은 원론적인 것만 정의하는 것이 아닌, 그 문법을 토대로 프로그램의 작성에 필요한 각종 기본적인 자료구조와 알고리즘, 입출력 기능들도 구현되어야 합니다.

C 언어의 경우 printf, fopen 등과 같이 주로 사용되는 함수들은 라이브러리 형태로 제공되고 있으며, 이들의 집합을 [C Run-time Libraries]라 합니다.

 

애초에 main 함수나 WinMain 함수가 호출되고 각종 인자를 전해주는 것부터 C/C++ 언어가 제공하는 런타임 라이브러리의 역할이며, 이는 결국 어떠한 프로그램이든 반드시 하나 이상의 런타임 라이브러리를 사용하게 된다는 의미가 됩니다.


CRT 링크 옵션의 비교

Visual Studio 에서 설정 가능한 CRT 옵션의 동작 방식 및 각각의 특징은 다음과 같습니다.

MD CRT 라이브러리를 DLL로 제공한다는 의미입니다.
이는 곧 CRT DLL이 없는 환경에서는 프로그램의 독립적인 실행이 불가능하다는 것을 말하며,
모든 실행파일 (EXE, DLL)이 공통으로 사용하는 공용 힙 메모리 공간이 생성됩니다.


컴파일러가 MSVCRT.lib를 .obj 파일에 포함도록 컴파일하며, 응용 프로그램은 특정 버전의 런타임 라이브러리 DLL을 사용합니다. 컴파일된 어플리케이션은 MSVCRT.lib에 정적으로 링크되는데, 이 라이브러리는 링커가 외부 참조를 확인할 수 있는 코드를 제공합니다.

런타임 라이브러리의 실제 동작하는 코드는 MSVCR{Version}.dll에 포함되어 있으며, 런타임 시에 MSVCRT.lib에 링크된 응용 프로그램에서 LoadLibrary, GetProcAddress 등의 API를 통해 이를 호출합니다.
MT CRT 라이브러리를 정적으로 링크한다는 의미입니다.
즉, CRT 라이브러리가 실행파일 (EXE, DLL)에 포함되며, 모든 실행파일(EXE, DLL)에 독립적인 CRT 힙 메모리가 생성됩니다.

컴파일러가 LIBCMT.lib를 .obj 파일에 포함하도록 컴파일하며, 응용 프로그램은 정적인 버전의 런타임 라이브러리를 사용합니다. 이를 통해 링커는 LIBCMT.lib를 참조하여 외부 기호를 확인할 수 있게 됩니다.

 

옵션 전처리기 링크 라이브러리 참조 DLL 특징
MD _MT, _DLL MSVCRT.lib MSVCR*.dll 멀티스레드, 동적 링크
MDd _MT, _DLL, _DEBUG MSVCRTD.lib MSVCR*D.dll 멀티스레드, 동적 링크 (debug)
MT _MT LIBCMT.lib N/A 멀티스레드, 정적 링크
MTd _MT, _DEBUG LIBCMTD.lib N/A 멀티스레드, 정적 링크 (debug)

 

옵션 장점 단점
MD CRT를 찾기 위한 최소한의 코드만 포함하며, 컴파일된 실행파일의 크기가 작습니다. CRT DLL이 없는 환경에서는 실행이 불가능합니다.
MT 독립적인 실행이 가능합니다. 실행에 필요한 모든 코드를 포함하므로 실행파일의 크기가 커집니다.

 


동적 링크와 정적 링크의 차이점

동적 링크

VC++ 컴파일러는 동적 링크 시 MSVCRT.dll을 내부적으로 MSVCR{Version}.dll로 재설정하여 컴파일합니다. 그리고 여기에서 {Version}은 개발 환경에 따라 DLL의 버전이 달라질 수 있음을 의미합니다.

따라서 서로 다른 환경에서 컴파일 된 EXE와 DLL 프로젝트의 경우 상이한 CRT 버전을 링크하고 있을 수 있으며, 이는 잠재적인 위험요소가 됩니다. 

 

외부에서 별도의 라이브러리나 DLL을 가져가 사용하는 경우 동적/정적 링크 여부를 확인하려면 dumpbin 명령어를 사용하면 됩니다.

 

dumpbin /all [application path] | find /i "msvcr"

아래 그림은 동적 링크를 사용하여 컴파일한 프로그램을 확인한 결과이며, 아무런 출력 결과가 없다면 정적 링크를 통해 컴파일 된 실행파일로 볼 수 있습니다.

동적 링크된 실행파일

정적 링크

MT 옵션 설정 시 각 실행파일에 CRT 힙 메모리가 모두 생성됩니다. 그래서 아래 그림의 foo.exe에서 할당한 힙 메모리를 bar.dll에서 해제한다면, 메모리의 Access Violation 에러가 발생하게 됩니다.

정적 링크 시 메모리 영역 구분

특히 자주 사용하는 CRT 객체를 생성하고 해제하는 부분에서 주의가 필요한데, 아래에 작성한 예제 코드와 테스트 결과를 통해 대략적인 동작 방식을 유추할 수 있습니다.

 

먼저, 파라미터로 전달 받은 FILE 객체를 해제하는 DLL Export 함수입니다.

#include <stdio.h>
extern "C" __declspec(dllexport) void writeFile(FILE *stream)
{
    char s[] = "this is a string";
    fprintf(stream, "%s", s);
    fclose(stream);
}

 

아래는 FILE 객체를 DLL로 전달하는 EXE 프로그램 입니다.

#include <stdio.h>
#include <process.h>
#pragma comment (lib, "CRT_test1_dll.lib")
extern "C" __declspec(dllimport) void writeFile(FILE *stream);

int main()
{
    FILE *stream;
    errno_-t err = fopen_s(&stream, "fprintf.out", "w");
    writeFile(stream);
    system("type fprintf.out");
    return 0;
}

 

위 코드에 대하여 CRT 설정이 가능한 4가지 경우의 테스트를 진행하였으며, 결과는 다음과 같습니다.

 

DLL 정적 링크 / EXE 정적 링크 fclose() 부분에서 에러 발생 (Critical Section 진입부에서 액세스 위반 발생)
DLL 정적 링크 / EXE 동적 링크 fclose() 부분에서 에러 발생 (Critical Section 진입부에서 액세스 위반 발생)
DLL 동적 링크 / EXE 정적 링크 fclose() 부분에서 에러 발생 (Critical Section 진입부에서 액세스 위반 발생)
DLL 동적 링크 / EXE 동적 링크 정상 동작

 

DLL과 EXE를 모두 동적 링크한 경우에서만 정상적으로 동작하였는데, 이는 아래 그림과 같이 실행파일이 동일한 힙 메모리 영역을 공유하기 때문입니다.

 

동적 링크 시 CRT 메모리 영역


결론

이론적으로 동적 링크 시 구성되는 실행파일 간에는 힙 메모리의 생성 및 소멸 위치가 문제를 발생시키지 않습니다. 그러나 메모리 영역의 생성과 소멸이 실행파일의 경계를 넘어가는 것은 안전하지 않은 개발 방식입니다.

 

또한, 현대의 HDD 용량에서 CRT 라이브러리를 정적으로 링크했을 때 추가되는 실행 파일의 용량은 사실 무의미한 변화로 볼 수 있습니다.

 

결론적으로 일반적인 개발 상황에서는 정적 링크를 설정하며, CRT 메모리 영역의 공유가 필요한 특수한(아직 저도 경험해보진 못했습니다^^;) 상황에서만 동적 링크 옵션을 사용하는 것이 적합하다고 보입니다.


참고 자료

제가 학생일 당시(^^;) 사용하던 Visual Studio 6.0 에서는 Single-Thread 옵션을 선택할 수 있었지만, 현재는 성능 및 안정성의 이유로 해당 옵션은 지원하지 않고 있습니다.

Visual Studio 6.0 에서의 런타임 라이브러리 설정 화면
Visual Studio 2010 에서의 런타임 라이브러리 설정 화면


자료 출처

 

'프로그래밍 > C++ & MFC' 카테고리의 다른 글

libcmt.lib 링크 충돌 문제  (0) 2021.12.29
DLL Main 구현 시 주의점  (0) 2021.07.11
함수 호출 규약  (0) 2021.07.07