함수 호출 규약이란
함수의 호출 규약은 Calling Convention 이라고 합니다.
함수의 파라미터(인자)를 어떠한 방식으로 전달하는지에대한 하나의 규약입니다.
함수를 호출 할 때, 프로세스에 정의되어있는 스택 메모리 공간을 이용하여 인자를 함수로 전달하게 되고,
이 스택 메모리 공간은 프로세스가 실행 될 때에 해당 PE헤더에 스택 메모리의 크기가 명시되어 있습니다.
스택에 저장되는 값들은 임시적인 값들이기 때문에 사용하지않더라도 값을 지우거나 하지 않습니다.
왜냐하면 굳이 지우려 하지않더라도 스택에 다른 값을 입력할 때 저절로 덮어 씌우고 갱신시키기 때문에 이에대한 불필요한 CPU 자원 소모의 낭비를 줄이기 위해서 방치 됩니다.
스택 메모리가 부족하다면 스택포인터(ESP)가 스택의 끝을 가리키게 되는데 이렇게된다면 스택영역을 사용할 수 없게 됩니다. 그렇기 때문에 함수를 사용한 후에 스택포인터(ESP)의 위치를 함수 시작 전으로 돌려 주면서 사용가능한 스택 메모리의 공간을 확보 하게 됩니다.
이때 스택포인터(ESP)가 정리하는지를 함수호출규약 이라고 합니다.
함수 호출 규약의 Caller & Calle
함수 호출 규약을 이해하기 위해서 필요한 것이 콜러와 콜리 입니다.
콜러(Caller) = 호출자 , 콜리(Callee) = 피호출자 를 의미합니다.
예시로 main() 함수에서 add() 라는 함수가 있다면 이때 콜러는 main()이며 콜리는 add() 가 됩니다.
cdecl (Caller가 직접 스택을 정리하는 방식)
cdecl 방식은 주로 C언어(C, C++) 에서 사용되는 방식 입니다.
콜러가 직접 스택을 정리하는 방식을 가지게 됩니다.
스택이 호출자에 의해 지워지기 때문에 가변 인자를 가지는 함수를 정의할 수 있습니다.
실행파일의 크기는 cdecl > stdcall 입니다. 이유는 각 함수 호출마다 스택을 정리하는 코드가 있어야하기때문에 그에 대한 공간만큼 크기가 더 커질 수 밖에 없습니다.
요소 | 구현 |
인수 전달 순서 | 오른쪽에서 왼쪽 |
스택 유지 관리 책임 | 호출하는 함수가 스택에서 인수를 꺼냅니다(정리합니다). |
이름 수식 컨벤션 (Name-decoration convention) |
C 연결을 사용하는 __cdecl 함수를 내보낼 때를 제외하고 이름 앞에 _가 붙습니다. (_함수명) |
대소문자 변환 규칙 | 수행되지 않습니다. |
#include "stdio.h"
int add(int a, int b) // add 함수 , 콜리
{
return (a + b);
}
int main(int argc, char* argv[]) // main 함수 , 콜러
{
return add(1, 2);
}
위의 예시는 콜러(main함수)에서 콜리(add함수)를 호출한 코드입니다. 콜리의 인자로 상수 1과 2를 넘겨줍니다.
위의 코드를 실행시켜 디버거로 살펴보면 401010[main]함수에서 파라미터 1과 2를 401000[add]로 넘겨주고
PUSH 2, PUSH 1 함수가 끝난 뒤 콜러였던 main함수에서 ADD ESP,8 코드를 통해서 스택포인터의 위치를 조정해 줍니다.
이처럼 콜러가 직접 스택을 정리하는 방식을 cdecl 이라고 합니다.
장점 : 가변 길이 파라미터를 전달 할 수 있다는 장점이 있습니다.
stdcall (Callee가 직접 스택을 정리하는 방식)
콜러가 직접 스택을 정리하는 방식과는 반대로 콜리가 직접 스택을 정리하는 방식이 있습니다.
Win32 API에서 사용되는 호출 규약 입니다.
Win32 API 의 경우 C언어로 만들어진 라이브러리이지만 호환성 때문에 stdcall 방식을 사용하고 있다고 합니다.
요소 | 구현 |
인수 전달 순서 | 오른쪽에서 왼쪽 |
인수 전달 컨벤션 (Argument-passing convention) |
포인터 또는 참조자 타입이 아닌 경우, 값 전달 |
스택 유지 관리 책임 | 피호출자가 스택에서 인수들을 꺼냅니다(정리합니다). |
이름 수식 컨벤션 | 함수명 앞에 _가 붙으며, 함수명 뒤에 인자 리스트의 바이트 크기를 @와 함께 표기합니다(_함수명@인자리스트크기) |
대소문자 변환 규칙 | 없음. |
#include "stdio.h"
int _stdcall add(int a, int b)
{
return (a + b);
}
int main(int argc, char* argv[])
{
return add(1, 2);
}
이번 예시코드는 add 함수가 _stdcall 이 명시되어져 있습니다.
stdcall 방식을 사용하고자 한다면 함수에 명시해주어야 합니다.
위의 코드를 실행시켜 디버거를 살펴본다면 이번에는 add함수에서 RETN 8 을 통해서 콜리가 직접 스택을 정리하는 모습을 볼 수 있습니다. (RETN 8 = RETN + POP 8 Bytes / 리턴 후 지정된 크기만큼 ESP가 증가)
stdcall방식의 장점은 호출되는 함수내에 스택을 정리하는 코드가 있기 떄문에 함수를 호출할때마다 ADD ESP, * 명령을 직접 하지 않아도 되기떄문에 코드가 간결해질 수 있습니다.
기본적인 호출규약의 정의와 cdecl & stdcall 에 대하여 알아보았습니다.
추가적인 규약으로
- fastcall
- thiscall
- vectorcall
- syscall
- pascal
- naked
등의 다양한 규약이 있습니다.
[참고 블로그]
'공부' 카테고리의 다른 글
[CS]캐시 메모리(캐시히트&캐시미스) (0) | 2023.04.20 |
---|---|
[CS]기초 (0) | 2023.04.19 |
[참고자료] 2D 게임 에셋 추출 하기 (0) | 2023.01.30 |
게임 에셋 검색 참조 사이트 (0) | 2023.01.30 |
[C++/DX11] - 텍스처 렌더링 (0) | 2023.01.27 |
댓글