markdown 함수호출규약(Calling Convention)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown 함수호출규약(Calling Convention)相关的知识,希望对你有一定的参考价值。
# 개요
비주얼 C/C++ 컴파일러는 여러가지 방법으로 함수를 호출한다. 호출 방법이 여러가지이기때문에
호출 방법에 대한 규정에 대하여 알아두고, 이해한다면 프로그램을 디버그하는 것과 코드를 어셈블리어와
링크하는 것에 도움이 될 것이다.
## Caller(호출자)와 Callee(피호출자)
아래의 그림을 보면 main()함수와 Sum()함수가 있다. 여기에서 main()함수는 Sum()함수를
호출하기 때문에 호출자가 되고, Sum()함수는 호출되기 때문에 피호출자가 된다.
![caller_callee](http://www.tipssoft.com/data/cheditor/0912/caller_callee.jpg)
# 호출 규정에 따른 인자 전달 순서
`__stdcall`과 `__cdecl`호출 규정을 따르는 함수는 호출시 두개 모두 오른쪽에서
왼쪽방향으로 매개인자를 스택프레임에 삽입한다. 예를 들면 위의 그림에서 main()
함수가 Sum(a, b) 함수를 호출할 때 스택프레임에는 변수 b가 먼저 삽입된 후 그
다음에 변수 a가 삽입된다.
아래의 표는 `__stdcall` 호출규정과 `__cdecl` 호출규정을 비교한 것이다.
![](http://www.tipssoft.com/data/cheditor/0912/__stdcall_vs___cdecl_copy1.jpg)
# 호출 규정에 따른 함수 호출 과정
`__stdcall`은 인자의 개수가 고정적인 Win32 API 함수를 호출하는데 사용하는
호출 규정이고, `__cdecl`은 인자의 개수가 가변적인 함수(printf() 등)를 호출하는데
사용되는 호출 규정이다. 이 두가지 호출 규정에 적용되는 함수가 다르기 때문에
이 함수들이 호출되는 과정도 다르다.
## `__stdcall`
![](http://www.tipssoft.com/data/cheditor/0912/__stdcall_copy.jpg)
`__stdcall` 호출 규정의 경우 함수 호출시 넘어가는 매개인자의 개수가 고정적이기
때문에 스택 프레임에 삽입, 삭제되는 인자들이 항상 일정하다. 그래서 함수 호출시
피호출자는 스스로 스택을 정리한 후 함수를 실행하고, 실행을 마친 후 자신이 삽입했던
인자들을 제거하면서 스택을 정리하고 함수를 빠져 나온다.
## `__cdecl`
![](http://www.tipssoft.com/data/cheditor/0912/__cdecl.jpg)
반면 `__cdecl` 호출 규정의 경우 함수 호출시 넘어가는 매개인자의 개수가 일정하지
않기때문에 피호출자는 스택을 스스로 정리하기 어렵다. 그래서 함수 호출시 호출자가
스택을 정리한 후 함수를 실행하고, 실행을 마친 후에 다시 호출자가 자신이 삽입했던
인자들을 제거하면서 스택을 정리한다.
## 번외: `__fastcall`
이 방식은 스택이 아닌 가까운 레지스터를 사용함으로써 호출 속도가 빠르며 피호출자가
스택을 정리하나 스택을 사용하지 않고 레지스터를 이용한다. 이 호출 규칙은 x86
아키텍처에만 적용된다.
그러나 왼쪽에서 오른쪽으로 인수 목록에서 발견된 처음 두 개의 DWORD 이하 인수만
ECX 및 EDX 레지스터로 전달되고, 다른 모든 인수는 오른쪽에서 왼쪽으로 스택에
전달된다.
이름 그대로 가장 빠른 호출 속도를 가지고 있으나, 초고속 그래픽 등 극한의 스피드를
요구하는 프로그래밍 등 외에는 __fastcall로 얻을 수 있는 속도 이득은 극히 미미하다.
하지만 빈번히 호출되는 함수라면 속도 이득이 없다고 할수 없으며 잘 사용하면 최적의
속도를 내는 프로그래밍에 유리하다.
## 같은점과 다른점
이 두가지 호출 규저에 따른 호출 방법은 결국 실행되는 코드수와 실행속도는 같지만
어셈블리어로 변환된 실행코드(혹은 실행파일)의 크기는 다르다.
`__stdcall` 호출 규정을 따르는 함수가 여러번 호출될 경우, 호출자에는 "call Sum"이
중복하여 발생하고, 스택에 인자를 삽입 삭제하는 부분은 피호출자에서 공통적으로
수행한다.
이에 반해 `__cdecl`호출 규정을 따르는 함수가 여러번 호출될 경우, 호출자는 스택에
인자를 삽입/삭제하는 부분이 함수를 호출할 때마다 발생하기때문에 `__stdcall`호출
규정보다 어셈블리어로 변환된 실행 코드의 크기가 커진다.
# 호출 규정에 따른 함수의 이름 변환
`__stdcall` 호출 규정을 따르는 함수의 선언부는 컴파일러에 의해 다음과 같은 일정한
패턴에 맞추어 바뀐다. 먼저 함수의 이름 앞에는 underscore(_)가 붙고, 함수의 이름
뒤에는 "@(앳 마크)"가 붙습니다. 그리고 "@" 뒤에는 10진 정수로 매개인자의 데이터
크기의 합을 표시해준다. 간단히 예를들면 아래와 같다.
```cpp
// int a의 데이터 크기가 4 이고, double b의 데이터 크기가 8 이므로 두 데이터 크기의 합은 12이다.
void __stdcall func(int a, double b) -> _func@12
```
반면 `__cdecl` 호출 규정의 경우 함수 이름 앞에 underscore(_)를 붙이지만 적용되는
함수가 가변인자 함수이기 때문에 매개인자의 데이터 크기의 합을 알기 힘드므로 "@"와
10진수의 데이터 크기부분을 표시하지 않는다. 위의 예를 __cdecl 에 적용하면
아래와 같다.
```cpp
void __cdecl func(int a, double b) -> _func
```
`__cdecl`의 경우 위의 코드와 같이 function 포인터의 이름이나 함수 이름 앞에
`__cdecl`키워드를 위치시킨다.
# 컴파일러 옵션에 대하여
`__cdecl` 은 C naming conventions(C 명명 규칙)와 C 호출 규정의 기본이기 때문에
`__cdecl` 이라고 명시하지 않으면 `__cdecl` 이 적용된다. 기본적으로 적용되는 호출
규정을 변경하려면 다음의 세가지 컴파일러 옵션 중 하나를 설정하면 된다.
- /Gz : 함수가 명시적으로 호출 규정을 선언하지 않았을 경우 기본으로 `__stdcall`
을 적용한다.
- /Gr : 함수가 명시적으로 호출 규정을 선언하지 않았을 경우 기본으로 `__fastcall`
을 적용한다.
- /Gd : 해당 함수의 호출 규정이 어떤 것이든 상관없이 강제적으로 `__cdecl`호출
규정을 적용한다.
# `#define`과 호출 규정
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
종종 위와 같이 정의된 함수를 본다. 여기에서 `LRESULT`는 반환타입이고, `WndProc`은
함수의 이름이다. 그렇다면 `CALLBACK`은 뭘까?
```cpp
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
```
`#define`으로 선언된 `CALLBACK`은 컴파일시 "__stdcall"로 대체되면서 위에 정의한 함수는
LRESULT __stdcall WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
로 대체되게 된다. 즉 `WndProc`함수는 `__stdcall`호출 규정을 따르는 함수임을
나타내는 것이다. CALLBACK 뿐만 아니라 위에 선언된 WINAPI 역시 함수 선언에서
사용하면 함수의 호출 규정을 `__stdcall` 로 적용하고, `WINAPIV`는 함수의 선언에서
사용하면 함수를 `__cdecl` 호출 규정으로 적용한다. 그 외의 키워드도 함수의 호출
규정을 적용할 때 사용된다.
# 각 규약 정리
|규약|내용|
|:---|---:|
|__cdecl | x86 구조에서 주로 사용(C/C++ 컴파일러에서 기본적으로 사용함), 함수 호출 시 오른쪽 인자부터 스택에 전달함, 호출자가 스택을 정리 함(add ESP, n)|
|__stdcall|MS Win32API 표준 규약, 함수 호출 시 오른쪽인자부터 스택에 전달함, 호출당한 함수가 사용한 스택을 정리함(ret n)|
|__fastcall|매개변수의 일부를 레지스터(ECX, EDX)로 전달함, 함수 호출 시 다른 규약에 비해 빠름, 호출당한 함수가 사용한 스택을 정리함(ret n)|
以上是关于markdown 함수호출규약(Calling Convention)的主要内容,如果未能解决你的问题,请参考以下文章