프로그래밍/C++ & MFC

함수 호출 규약

단칸이 2021. 7. 7. 23:53

함수 호출 규약

함수를 호출하면 파라미터/복귀 주소/지역 변수 등을 위한 공간이 Stack 영역에 할당되며, 함수 호출이 끝나면 할당된 Stack 영역이 해제됩니다.

이 때 파라미터의 경우 함수 호출자 측에서 정리할지 / 피호출자 측에서 정리할지 결정이 필요한데, 여기에 사용되는 규칙이 함수 호출 규약입니다.


함수 호출 규약의 종류

※ Visual C/C++ 컴파일러가 지원하는 함수 호출 규약의 종류

Keyword Stack 정리 위치 인자 전달 순서 인자 전달 매체
__cdecl Caller Stack
__clrcall N/A Stack
__stdcall Callee Stack
__fastcall Callee 레지스터 + Stack
__thiscall Callee Stack
_vectorcall Callee 레지스터 + Stack

__cdecl

C/C++ 기본 호출 규약으로, Visual Studio의 기본 옵션으로 설정되어 있습니다.

Caller가 파라미터를 정리하는 방식을 사용하는데, 이는 곧 가변 인자를 지원한다는 것을 의미합니다. 호출한 함수에서는 가변 인자의 Stack 크기를 알지만, 호출된 함수에서는 가변 인자의 크기를 모르기 때문입니다.

이 방식의 대표적인 예로 printf() 함수 군이 있습니다.

__stdcall

Win32 API의 표준 호출 규약입니다. 윈도우 프로그래밍에서 흔히 볼 수 있는 키워드 중 WINAPI, APIENTRY, CALLBACK 등은 모두 __stdcall을 지칭하는 define 문으로 아래와 같이 선언되어 있습니다.

#define CALLBACK __stdcall
#define WINAPI __stdcall
#define APIENTRY WINAPI

WinMain이나 WndProc, 스레드 함수 등 CALLBACK 함수를 호출하는 주체는 어플리케이션이 아닌 운영체제 입니다. 그런데 스레드 함수의 경우 종료 시 반환되는 지점이 운영체제 내부에 있는 것이 아니기 때문에, 호출된 함수 끝에서 Stack을 정리할 수 있도록 __stdcall 규약을 사용합니다.

__fastcall

전체적인 구조는 stdcall과 유사합니다. 다만, 빠른 속도를 위해 레지스터를 사용한다는 특징이 있습니다.

Instruction을 실행할 때 CPU는 외부의 메모리(RAM)에서 명령어를 불러오는 것보다 CPU 내부의 레지스터를 활용하는 것이 성능적 측면에서 우월하기 때문에 이러한 방식을 지원합니다.


Name mangling

함수 호출 규약에 따라 Name mangling이 다르게 적용되기 때문에, DLL에 Export 된 함수 호출 시 주의가 필요합니다. 

다음은 각 함수 호출 규약에 따른 Name mangling 결과입니다.

extern "C" __declspec(dllexport) void DefaultFunction(int arg1, int arg2);
extern "C" __declspec(dllexport) void __cdecl CdeclFunction(int arg1, int arg2);
extern "C" __declspec(dllexport) void __stdcall StdcallFunction(int arg1, int arg2);
extern "C" __declspec(dllexport) void __fastcall FastcallFunction(int arg1, int arg2);

Depends를 통해 확인한 Name mangling 결과

cdecl 방식을 제외한 다른 호출 규약을 사용한 경우 Name mangling이 이루어지는 것을 확인할 수 있습니다. 따라서 위에서 export된 함수들을 사용하는 어플리케이션에서 GetProcAddress와 같은 API를 사용하려면 정확한 함수 명을 알아야 한다는 문제가 생깁니다.

 

언뜻 보기에 이 문제는 디폴트 방식인 cdecl을 이용하면 될 것으로 보이지만, 이 때 간과하지 말아야 하는 점이 해당 DLL이 사용되는 사용처입니다. cdecl 방식을 이용하여 함수들을 구현할 경우 CALLBACK 함수와 같이 운영체제가 호출해줄 수도 없으며, stdcall 방식을 사용하는 다른 프로그래밍 언어와 호환을 맞추기 어려워 집니다.

 

반면, stdcall은 Windows의 표준 규약이기 때문에 다른 언어와의 혼합 프로그래밍이 간편해집니다. 호출 규약만 맞춰준다면 개발 툴이나 언어에 상관없이 제작된 DLL을 공유할 수 있기 때문입니다.

 

그런데 우리는 Name mangling에 관계 없이 정의된 함수 명을 호출해서 사용하곤 합니다.

이는 해당 DLL을 사용하는 프로그램들에게 함수 명을 명시적으로 지정해주는 .def 파일이 함께 빌드되어 찾아갈 수 있기 때문입니다.


자료 출처

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

libcmt.lib 링크 충돌 문제  (0) 2021.12.29
CRT (C Run-time Libraries)  (0) 2021.07.12
DLL Main 구현 시 주의점  (0) 2021.07.11