함수를 호출할 때 전달되는 파라미터는 함수 호출이 종료되고 나면 스택에서 정리되어야 한다.
calling convention이라 불리는 함수 호출 규약은 인자를 전달하고 스택에서 정리하는 방식에 따라 구별한 것이다.
C 언어에 기본으로 적용된다.
명시적으로는 __cdecl로 선언하게 된다.
스택에 파라미터를 전달한다.
이 규약에서 스택을 정리하는 것은 caller이다.
즉 main이 printf를 호출했다면 그 스택도 main이 정리하는 것이다.
예를 들면 다음과 같다. main 함수의 스택프레임에서
call printf
add esp, 8 이게 스택포인터를 8 밑으로 내려서 스택을 정리하는 것
pop ebp
장점 : 가변 길이 파라미터를 전달할 수 있다. 다른 컨벤션에서는 구현이 어렵다.
standard call이라고도 한다. Windows API에 적용되는데 C에서 작성한 코드에서 컴파일하고 싶을 땐 _stdcall을 붙여주면 된다.
stdcall 역시 스택에 인자를 전달하게 되는데 이 호출 규약에선 callee가 스택을 직접 정리하게 된다. 쉽게 말해서 다 놀고 방 잘 치우고 나오는 그런 착한 아이.
메인의 스택프레임에서
call func1을 하게 되면
func1의 스택프레임에서
~~
pop ebp
retn 8 <- 이게 스스로 스택을 정리한 것이다.
stdcall의 장점은 코드 크기가 작아진다는 것이다.
callee 안에 자체적으로 스택 정리코드가 존재하므로 caller가 함수 호출때마다 'add esp 스택크기' 를 써줘야 하는 cdecl보다 코드 크기가 작아질 수 있다. Win32 API는 c로 된 라이브러리지만 stdcall 방식을 사용하는데 이것은 c 이외의 언어에서 api를 직접 호출할 때 호환성을 좋게 하기 위해서라고 한다.
fastcall은 기본적으로는 stdcall과 같은 방식이지만 몇가지 차이가 있다. 일단 파라미터를 전달하는 대상이 스택뿐만 아니라 레지스터(ecx, edx)까지 있다. 예를 들어 함수 파라미터가 4개 필요하다면, 앞의 두개는 레지스터를 이용해서 전달한다.
이렇게 레지스터를 이용하다 보니 fastcall은 빠른 함수 호출이 가능하다. CPU에서 멀리 있는 메모리보다 가깝게 붙어있는 레지스터에 접근하는 것이 훨씬 빠를 수 있기 때문이다.
물론 레지스터를 관리해야 한다는 추가적인 오버헤드가 발생할 수 있기도 하다. ecx랑 edx를 언제나 편하게 불러낼 수 있는 것은 아니기 때문이다. 가령 ecx에 중요한 값이 미리 존재한다면 그 값을 다른 곳에 옮겨서 따로 저장해야 할 필요가 생길 수도 있다.
stdcall과 비슷하다고 한 것처럼 이것 역시 스택을 callee가 직접 정리하고 나온다.
메인의 스택프레임에서
mov edx, 2
mov ecx, 1 <- fastcall은 변수를 레지스터에 전달할 수 있다.
call func2
그럼 func2의 스택프레임에선
~~
mov [ebp-8], edx
mov [ebp-4], ecx <-레지스터에 들어온 파라미터를 스택에 옮기고
~~필요한 연산하고
mov esp, ebp <-esp를 끌어내려서 직접 스택을 정리한다
pop ebp
retn