program story

stdcall 및 cdecl

inputbox 2020. 9. 25. 07:49
반응형

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그 반대의 경우 든 상관 없다는 게시물을 발견했습니다 . 그렇습니다.

The reason: with __cdecl the arguments that are passed to the called functions are removed form the stack by the calling function, in __stdcall, the arguments are removed from the stack by the called function. If you call a __cdecl function with a __stdcall, the stack is not cleaned up at all, so eventually when the __cdecl uses a stacked based reference for arguments or return address will use the old data at the current stack pointer. If you call a __stdcall function from a __cdecl, the __stdcall function cleans up the arguments on the stack, and then the __cdecl function does it again, possibly removing the calling functions return information.

The Microsoft convention for C tries to circumvent this by mangling the names. A __cdecl function is prefixed with an underscore. A __stdcall function prefixes with an underscore and suffixed with an at sign “@” and the number of bytes to be removed. Eg __cdecl f(x) is linked as _f, __stdcall f(int x) is linked as _f@4 where sizeof(int) is 4 bytes)

If you manage to get past the linker, enjoy the debugging mess.


I want to improve on @adf88's answer. I feel that pseudocode for the STDCALL does not reflect the way of how it happens in reality. 'a', 'b', and 'c' aren't popped from the stack in the function body. Instead they are popped by the ret instruction (ret 12 would be used in this case) that in one swoop jumps back to the caller and at the same time pops 'a', 'b', and 'c' from the stack.

Here is my version corrected according to my understanding:

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)


It's specified in the function type. When you have a function pointer, it's assumed to be cdecl if not explicitly stdcall. This means that if you get a stdcall pointer and a cdecl pointer, you can't exchange them. The two function types can call each other without issues, it's just getting one type when you expect the other. As for speed, they both perform the same roles, just in a very slightly different place, it's really irrelevant.


The caller and the callee need to use the same convention at the point of invokation - that's the only way it could reliably work. Both the caller and the callee follow a predefined protocol - for example, who needs to clean up the stack. If conventions mismatch your program runs into undefined behavior - likely just crashes spectacularly.

This is only required per invokation site - the calling code itself can be a function with any calling convention.

You shouldn't notice any real difference in performance between those conventions. If that becomes a problem you usually need to make less calls - for example, change the algorithm.


Those things are Compiler- and Platform-specific. Neither the C nor the C++ standard say anything about calling conventions except for extern "C" in C++.

how does a caller know if it should free up the stack ?

The caller knows the calling convention of the function and handles the call accordingly.

At the call site, does the caller know if the function being called is a cdecl or a stdcall function ?

Yes.

How does it work ?

It is part of the function declaration.

How does the caller know if it should free up the stack or not ?

The caller knows the calling conventions and can act accordingly.

Or is it the linkers responsibility ?

No, the calling convention is part of a function's declaration so the compiler knows everything it needs to know.

If a function which is declared as stdcall calls a function(which has a calling convention as cdecl), or the other way round, would this be inappropriate ?

No. Why should it?

In general, can we say that which call will be faster - cdecl or stdcall ?

I don't know. Test it.


a) When a cdecl function is called by the caller, how does a caller know if it should free up the stack?

The cdecl modifier is part of the function prototype (or function pointer type etc.) so the caller get the info from there and acts accordingly.

b) If a function which is declared as stdcall calls a function(which has a calling convention as cdecl), or the other way round, would this be inappropriate?

No, it's fine.

c) In general, can we say that which call will be faster - cdecl or stdcall?

In general, I would refrain from any such statements. The distinction matters eg. when you want to use va_arg functions. In theory, it could be that stdcall is faster and generates smaller code because it allows to combine popping the arguments with popping the locals, but OTOH with cdecl, you can do the same thing, too, if you're clever.

The calling conventions that aim to be faster usually do some register-passing.


Calling conventions have nothing to do with the C/C++ programming languages and are rather specifics on how a compiler implements the given language. If you consistently use the same compiler, you never need to worry about calling conventions.

However, sometimes we want binary code compiled by different compilers to inter-operate correctly. When we do so we need to define something called the Application Binary Interface (ABI). The ABI defines how the compiler converts the C/C++ source into machine-code. This will include calling conventions, name mangling, and v-table layout. cdelc and stdcall are two different calling conventions commonly used on x86 platforms.

By placing the information on the calling convention into the source header, the compiler will know what code needs to be generated to inter-operate correctly with the given executable.

참고URL : https://stackoverflow.com/questions/3404372/stdcall-and-cdecl

반응형