stdcall 및 cdecl 해제해야하는지 어떻게 알 수 있습니까? 호출 사이트에서

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

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



답변

레이몬드 첸은 무엇의 좋은 개요를 제공 __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) 둘 사이에 실제 성능 차이가 없어야합니다.


답변

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)


답변

나는 당신이 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 바이트

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


답변

@ 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)


답변

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


답변

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

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

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


답변

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

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

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

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

예.

어떻게 작동합니까?

함수 선언의 일부입니다.

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

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

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

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

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

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

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

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