사용자 도구

사이트 도구


kb:callingconvention

Calling Convention?

함수 호출 방식(Calling Convention)이란 호출자(caller, 피호출자 함수를 호출하는 함수)와 피호출자(callee, 호출자로부터 호출되는 함수) 간에 미리 정해둔, 파라미터의 전달 순서와 사용이 끝난 후의 스택 정리에 대한 규약이라고 할 수 있다. WIN32 환경에서는 기본적으로 세 가지의 호출 방식이 존재한다.

  • Standard Call – 파라미터들은 코드 상의 오른쪽 파라미터부터 먼저 푸쉬되고(첫번째 파라미터가 스택의 맨 위로 오게 된다는 말이다.), 피호출자(callee)가 스택을 정리한다.
  • CDECL or C Calling Convention – 파라미터들은 코드 상의 오른쪽 파라미터부터 먼저 푸쉬되고(첫번째 파라미터가 스택의 맨 위로 오게 된다는 말이다.), 호출자(caller)가 스택을 정리한다.
  • Fast Call – C++의 내부 구조에 익숙하다면, 멤버 함수를 호출하기 위해서는 this 포인터가 반드시 필요하다는 것을 알 것이다. 일반적으로 이 this 포인터가 스택에 최초로 푸쉬되는 파라미터다. 하지만 이 방식에서는 this 포인터를 스택에다가 푸쉬하는 게 아니라, 레지스터(ECX)에 저장한다. 파라미터들은 코드 상의 오른쪽 파라미터부터 먼저 푸쉬되고(첫번째 파라미터가 스택의 맨 위로 오게 된다는 말이다.), 피호출자(callee)가 스택을 정리한다.
  • Pascal Calling Convention – 이 방식은 더 이상 사용할 수 없다. 이는 모두 Standard Call로 대체되었다. 어쨌든 원래의 Pascal Calling Convention은 파라미터를 코드 상의 왼쪽 것부터 먼저 푸쉬하고, 피호출자(callee)가 스택을 정리한다.

결국 파라미터의 전달 순서와 스택 정리에 대한 규약이 다르므로, 다른 규약을 이용해서 만들어진 DLL 안에 있는 함수를 헤더 같은 것도 없이 동적으로 바인딩해서 사용하는 경우, 그것을 컴파일러에게 명시적으로 가르쳐줘야하는 것이다. (호출하는 측과 호출받는 쪽이 다른 Calling Convention을 사용할 수 있으므로…)

스택 정리를 누가 하든 무슨 상관인가?

1. 호출자가 정리 - C Calling Convention

Invoke MyFunction, 1, 2, 3, 4

코드가 아래와 같이 변환된다.

PUSH    4  
PUSH    3  
PUSH    2  
PUSH    1  
CALL    MyFunction
ADD     sp, 16      ;; -->> 스택 정리 코드..  
  • 장점 : 호출하는 측에서 스택에다 몇 개의 파라미터를 집어넣었는지 알고, 스택 정리를 담당하므로, 가변 인자(variable arguments)를 사용할 수 있다.
  • 단점 : 프로그램의 크기가 커진다. 호출자가 스택을 정리한다는 말은, 위에서도 보듯이 함수 호출할 때마다 스택 정리하는 코드가 들어가야 한다는 말이고, 이는 프로그램의 크기 증가와 속도 감소를 가져온다.

printf 함수 같은 것이 가변 인자를 사용하는 대표적인 예이다. 하지만 피호출자는 인자가 몇개나 전달되었는지 정확히 알 수 없다. 그냥 포맷 문자열 같은 것을 이용해서 추측할 뿐이다. 예를 들어 printf 함수를 호출할 때, ”%i %i %i” 문자열을 주면, printf 함수는 호출하는 측에서 추가적으로 스택에다 3개의 인자를 집어넣지 않은 경우(프로그래머의 실수!)에도, 스택에 있는 3개의 값을 이용해 문자열을 생성할 것이다. 이것이 크래시를 일으킬지, 아닐지는 정확히 알 수 없다. 3개 이상의 인수를 전달하는 경우는 어차피 호출하는 측에서 스택을 정리하므로 문제가 되지 않는다.

2. 피호출자가 정리 - Standard Calling Convention

Invoke MyFunction, 1, 2, 3, 4

코드가 아래와 같이 변환된다.

PUSH    4  
PUSH    3  
PUSH    2  
PUSH    1  
CALL    MyFunction
  • 장점 : 피호출자가 스택 정리를 담당한다면, 프로그램의 크기는 작아진다. 또한 스택 정리 명령어를 매번 호출하지 않아도 되므로, 속도 또한 빨라진다.
  • 단점 : 피호출자가 자신이 정리할 스택의 크기(파라미터들의 총 크기)를 정확히 알고 있어야 하므로, 가변 인자를 사용할 수 없다.

차이점?

호출자가 하든, 피호출자가 하든 어딘가에서는 스택을 정리해야하지 않는가? 즉 위에서 없어진

ADD sp, 16

는 어차피 피호출자, 즉

MyFunction

내부에서 호출해야 하지 않느냐는 말이다. 어차피 어디에선가 호출해야 한다면 프로그램의 크기가 왜 작아지며, 빨라지는가? 이는 인텔 계열에서 지원하는 특수한 명령어 때문이다. 즉 리턴하면서 스택 정리를 동시에 하는 명령어가 있기 때문에, 프로그램의 크기가 작아지고, 빨라진다는 말이다.

정리

from http://www.smkang.com

Calling Conventions Arguments Passing Stack Maintenance Name Decorations Notes
cdecl Right → Left 호출자가 Stack에서 인자를 제거한다. 함수 이름 앞에 _를 붙인다. Ex) _foo C/C++함수의 기본 호출 규약
stdcall Right → Left 호출된 함수가 Stack에서 인자를 제거한다. _가앞에 붙고 뒤에 @와 인자의 크기가 10진수로 붙는다. Ex) _foo@12 대부분의 System 함수가 사용. VB에서 내부함수가 사용.
fastcall 첫번째 2개의 DWORD 파라미터는 ECX, EDX 레지스터 사용. 나머지는 Right→Left 호출자가 Stack에서 인자를 제거한다. @이 앞에 붙고 @과 인자의 크기가 10진수로 뒤에 붙는다. Ex) @foo@12 Intel CPU 만 사용. Borland의 Delphi 컴파일러가 사용.
this Right → Left this 매개변수는 ECX 레지스터사용. 호출자가 Stack에서 인자를 제거한다. None C++클래스의 멤버 함수가 사용. COM에서 사용.
naked Right → Left 호출자가 Stack에서 인자를 제거한다. None VxD에서 사용. Custom Prolog 와 Epilog를 만들때 사용.

링크

kb/callingconvention.txt · 마지막으로 수정됨: 2014/11/10 09:33 (바깥 편집)