stdcall 및 cdecl


92

두 가지 유형의 호출 규칙 ( stdcallcdecl)이 있습니다. 나는 그들에 대해 몇 가지 질문이 있습니다.

  1. cdecl 함수가 호출 될 때 호출자는 스택을 해제해야하는지 어떻게 알 수 있습니까? 호출 사이트에서 호출자가 호출되는 함수가 cdecl인지 stdcall 함수인지 알고 있습니까? 어떻게 작동합니까? 호출자는 스택을 해제해야하는지 여부를 어떻게 알 수 있습니까? 아니면 링커의 책임입니까?
  2. stdcall로 선언 된 함수가 함수 (cdecl로 호출 규칙이 있음)를 호출하거나 그 반대의 경우 이것이 부적절합니까?
  3. 일반적으로 cdecl 또는 stdcall 중 어느 호출이 더 빠를 것이라고 말할 수 있습니까?

9
많은 유형의 호출 규칙이 있으며 그중 두 가지입니다. en.wikipedia.org/wiki/X86_calling_conventions
Mooing Duck 2014 년

1
정답을 표시하세요
ceztko aug.

답변:


79

레이몬드 첸은 무엇의 좋은 개요를 제공 __stdcall하고 __cdecl수행을 .

(1) 컴파일러가 해당 함수의 호출 규칙을 알고 필요한 코드를 생성하기 때문에 호출자는 함수를 호출 한 후 스택을 정리하는 것을 "알고"있습니다.

void __stdcall StdcallFunc() {}

void __cdecl CdeclFunc()
{
    // The compiler knows that StdcallFunc() uses the __stdcall
    // convention at this point, so it generates the proper binary
    // for stack cleanup.
    StdcallFunc();
}

다음 과 같이 호출 규칙이 일치하지 않을 수 있습니다 .

LRESULT MyWndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam);
// ...
// Compiler usually complains but there's this cast here...
windowClass.lpfnWndProc = reinterpret_cast<WNDPROC>(&MyWndProc);

너무 많은 코드 샘플이 잘못되어 재미조차 없습니다. 다음과 같아야합니다.

// CALLBACK is #define'd as __stdcall
LRESULT CALLBACK MyWndProc(HWND hwnd, UINT msg
    WPARAM wParam, LPARAM lParam);
// ...
windowClass.lpfnWndProc = &MyWndProc;

그러나 프로그래머가 컴파일러 오류를 무시하지 않는다고 가정하면 컴파일러는 관련된 함수의 호출 규칙을 알기 때문에 스택을 적절하게 정리하는 데 필요한 코드를 생성합니다.

(2) 두 가지 방법 모두 작동합니다. 실제로 이것은 __cdeclVisual C ++ 컴파일러에 따라 C 및 C ++ 프로그램에 대한 기본값 이고 WinAPI 함수가 __stdcall규칙을 사용 하기 때문에 Windows API와 상호 작용하는 코드에서 적어도 매우 자주 발생 합니다 .

(3) 둘 사이에 실제 성능 차이가 없어야합니다.


좋은 예와 대회 역사를 부르는 Raymond Chen의 게시물에 +1. 관심있는 사람이라면 다른 부분도 읽을 수 있습니다.
OregonGhost

Raymond Chen에게 +1. Btw (OT) : 블로그 검색 창에서 다른 부분을 찾을 수없는 이유는 무엇입니까? Google은 그들을 찾았지만 MSDN 블로그는 찾지 못합니까?
Nordic Mainframe

44

CDECL 인수는 역순으로 스택에 푸시되고 호출자는 스택을 지우고 결과는 프로세서 레지스트리를 통해 반환됩니다 (나중에 "레지스터 A"라고 부릅니다). STDCALL에는 한 가지 차이점이 있습니다. 호출자는 스택을 지우지 않고 호출자가 수행합니다.

어느 것이 더 빠른지 묻습니다. 아무도. 가능한 한 기본 호출 규칙을 사용해야합니다. 특정 규칙을 사용해야하는 외부 라이브러리를 사용할 때 탈출구가없는 경우에만 규칙을 변경하십시오.

게다가 컴파일러가 기본값으로 선택할 수있는 다른 규칙이 있습니다. 즉, Visual C ++ 컴파일러는 프로세서 레지스터를 더 광범위하게 사용하기 때문에 이론적으로 더 빠른 FASTCALL을 사용합니다.

일반적으로 일부 외부 라이브러리에 전달 된 콜백 함수에 적절한 호출 규칙 서명을 제공해야합니다. 즉 qsort, C 라이브러리에서 콜백 은 CDECL이어야합니다 (컴파일러가 기본적으로 다른 규칙을 사용하는 경우 콜백을 CDECL로 표시해야 함) 또는 다양한 WinAPI 콜백이 STDCALL (전체 WinAPI는 STDCALL 임).

다른 일반적인 경우는 일부 외부 함수에 대한 포인터를 저장할 때일 수 있습니다. 즉, WinAPI 함수에 대한 포인터를 만들려면 해당 유형 정의가 STDCALL로 표시되어야합니다.

다음은 컴파일러가 수행하는 방법을 보여주는 예입니다.

/* 1. calling function in C++ */
i = Function(x, y, z);

/* 2. function body in C++ */
int Function(int a, int b, int c) { return a + b + c; }

CDECL :

/* 1. calling CDECL 'Function' in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call (jump to function body, after function is finished it will jump back here, the address where to jump back is in registers)
move contents of register A to 'i' variable
pop all from the stack that we have pushed (copy of x, y and z)

/* 2. CDECL 'Function' body in pseudo-assembler */
/* Now copies of 'a', 'b' and 'c' variables are pushed onto the stack */
copy 'a' (from stack) to register A
copy 'b' (from stack) to register B
add A and B, store result in A
copy 'c' (from stack) to register B
add A and B, store result in A
jump back to caller code (a, b and c still on the stack, the result is in register A)

STDCALL :

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then a copy of 'y', then a copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */
pop 'a' from stack to register A
pop 'b' from stack to register B
add A and B, store result in A
pop 'c' from stack to register B
add A and B, store result in A
jump back to caller code (a, b and c are no more on the stack, result in register A)

참고 : __fastcall은 __cdecl보다 빠르며 STDCALL은 Windows 64 비트의 기본 호출 규칙입니다
dns

오; 그래서 그것은 반환 주소를 팝하고, 인수 블록 크기를 추가 한 다음, 이전에 팝된 반환 주소로 점프해야합니까? 스택을 청소하지 않은 문제로 다시 돌아가서 스택에 다시
Dmitry

또는,베이스 포인터에 REG1 복귀, 세트 스택 포인터를 팝업, 다음 REG1로 이동
드미트리

양자 택일로, 이동 스택 포인터 값을 스택의 상단에서 하단에, 깨끗하고, 다음 RET 전화
드미트리

15

나는 당신이 a __stdcall에서 전화를해도 상관 없다는 글을 보았다 .__cdecl 하거나 그 반대의 경우도 . 그렇습니다.

이유 : __cdecl호출 된 함수에 전달 __stdcall된 인수가 호출 함수에 의해 스택에서 제거되면에서 인수는 호출 된 함수에 의해 스택에서 제거됩니다. 를 사용하여 __cdecl함수 를 호출 __stdcall하면 스택이 전혀 정리되지 않으므로 결국 __cdecl인수 또는 반환 주소에 스택 기반 참조를 사용할 때 현재 스택 포인터의 이전 데이터를 사용합니다. 당신이 호출하면 __stdcallA로부터 기능 __cdecl__stdcall함수는하여 스택에 인수 및 정리__cdecl 기능은 아마도 호출 기능 정보를 반환 제거, 다시 않습니다.

C에 대한 Microsoft 규칙은 이름을 변경하여이를 우회하려고합니다. __cdecl기능은 밑줄로 시작된다. __stdcall밑줄 및 접미사와 기호 "@"와 바이트의 수와 기능 접두어를 제거 할 수 있습니다. 예 __cdeclF (X)로 연결되어있다가 _f, __stdcall f(int x)로 연결되는 _f@4sizeof(int)) 4 바이트

링커를 통과 할 수 있다면 디버깅 혼란을 즐기십시오.


3

@ adf88의 답변을 개선하고 싶습니다. 나는 STDCALL에 대한 의사 코드가 실제로 어떻게 일어나는지를 반영하지 않는다고 생각합니다. 'a', 'b'및 'c'는 함수 본문의 스택에서 팝되지 않습니다. 대신 한 번에 호출자에게 다시 점프하고 동시에 스택에서 'a', 'b'및 'c'를 팝하는 ret명령 ( ret 12이 경우에 사용됨)에 의해 팝됩니다 .

내 이해에 따라 수정 된 버전은 다음과 같습니다.

STDCALL :

/* 1. calling STDCALL in pseudo-assembler (similar to what the compiler outputs) */
push on the stack a copy of 'z', then copy of 'y', then copy of 'x'
call
move contents of register A to 'i' variable

/* 2. STDCALL 'Function' body in pseaudo-assembler */ copy 'a' (from stack) to register A copy 'b' (from stack) to register B add A and B, store result in A copy 'c' (from stack) to register B add A and B, store result in A jump back to caller code and at the same time pop 'a', 'b' and 'c' off the stack (a, b and c are removed from the stack in this step, result in register A)


2

함수 유형에 지정됩니다. 함수 포인터가있을 때 명시 적으로 stdcall이 아니라면 cdecl로 간주됩니다. 즉, stdcall 포인터와 cdecl 포인터가 있으면 교환 할 수 없습니다. 두 함수 유형은 문제없이 서로를 호출 할 수 있으며, 다른 유형을 예상 할 때 하나의 유형 만 가져옵니다. 속도에 관해서는 둘 다 동일한 역할을 수행합니다. 아주 약간 다른 곳에서 정말 관련이 없습니다.


1

호출자와 수신자는 호출 시점에서 동일한 규칙을 사용해야합니다. 이것이 안정적으로 작동 할 수있는 유일한 방법입니다. 호출자와 수신자 모두 미리 정의 된 프로토콜 (예 : 스택을 정리해야하는 사람)을 따릅니다. 규칙이 일치하지 않으면 프로그램이 정의되지 않은 동작으로 실행됩니다.

이것은 호출 사이트마다 필요합니다. 호출 코드 자체는 모든 호출 규칙을 가진 함수가 될 수 있습니다.

이러한 규칙간에 성능의 실제 차이를 눈치 채면 안됩니다. 이것이 문제가되는 경우 일반적으로 호출을 줄여야합니다 (예 : 알고리즘 변경).


1

이러한 것들은 컴파일러 및 플랫폼에 따라 다릅니다. C와 C ++ 표준은 C ++를 제외하고는 호출 규칙에 대해 아무 말도하지 않습니다 extern "C".

호출자는 스택을 해제해야하는지 어떻게 알 수 있습니까?

호출자는 함수의 호출 규칙을 알고 그에 따라 호출을 처리합니다.

호출 사이트에서 호출자가 호출되는 함수가 cdecl인지 stdcall 함수인지 알고 있습니까?

예.

어떻게 작동합니까?

함수 선언의 일부입니다.

호출자는 스택을 해제해야하는지 여부를 어떻게 알 수 있습니까?

호출자는 호출 규칙을 알고 그에 따라 조치를 취할 수 있습니다.

아니면 링커의 책임입니까?

아니요, 호출 규칙은 함수 선언의 일부이므로 컴파일러는 알아야 할 모든 것을 알고 있습니다.

stdcall로 선언 된 함수가 함수 (cdecl로 호출 규칙이 있음)를 호출하거나 그 반대의 경우 이것이 부적절합니까?

아니요. 왜 그래야합니까?

일반적으로 cdecl 또는 stdcall 중 어느 호출이 더 빠를 것이라고 말할 수 있습니까?

모르겠어요. 그것을 테스트하십시오.


0

a) 호출자가 cdecl 함수를 호출 할 때 호출자는 스택을 해제해야하는지 어떻게 알 수 있습니까?

cdecl수정은 함수 프로토 타입의 일부 (또는 함수 포인터 유형 등) 발신자가에서 정보를 얻을하고 그에 따라 동작하기 때문에입니다.

b) stdcall로 선언 된 함수가 함수 (cdecl로 호출 규칙이 있음)를 호출하거나 그 반대의 경우 이것이 부적절합니까?

아니, 괜찮아.

c) 일반적으로 cdecl 또는 stdcall 중 어떤 호출이 더 빠를 것이라고 말할 수 있습니까?

일반적으로 나는 그러한 진술을 자제합니다. 구별이 중요합니다. va_arg 함수를 사용하려는 경우. 이론적으로는stdcall 는 인수 팝핑과 로컬 팝핑을 결합 할 수 있기 때문에 더 빠르고 작은 코드를 생성 할 수 있지만 OTOH는cdecl 사용하면 영리하다면 동일한 작업을 수행 할 수도 있습니다.

더 빠른 것을 목표로하는 호출 규칙은 일반적으로 레지스터 통과를 수행합니다.


-1

호출 규칙은 C / C ++ 프로그래밍 언어와 관련이 없으며 컴파일러가 주어진 언어를 구현하는 방법에 대한 구체적인 내용입니다. 동일한 컴파일러를 지속적으로 사용하는 경우 호출 규칙에 대해 걱정할 필요가 없습니다.

그러나 때때로 우리는 다른 컴파일러에 의해 컴파일 된 바이너리 코드가 올바르게 상호 운용되기를 원합니다. 그렇게 할 때 우리는 ABI (Application Binary Interface)라는 것을 정의해야합니다. ABI는 컴파일러가 C / C ++ 소스를 기계 코드로 변환하는 방법을 정의합니다. 여기에는 호출 규칙, 이름 맹 글링 및 v-table 레이아웃이 포함됩니다. cdelc 및 stdcall은 x86 플랫폼에서 일반적으로 사용되는 두 가지 다른 호출 규칙입니다.

호출 규칙에 대한 정보를 소스 헤더에 배치함으로써 컴파일러는 주어진 실행 파일과 올바르게 상호 운용하기 위해 생성해야하는 코드를 알 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.