Unterschied bei normaler Funktion und stdcall-Funktion



  • Hallo,

    ich habe mich schon seit längerem gefragt, welcher Unterschied zwischen einer normalen Funktion ala:

    int foo(param1, param2)
    

    und einer stdcall-Funktion ala:

    int __stdcall foo(param1, param2)
    

    liegt.
    Hat dies Auswirkungen auf den Stackaufbau (da ich auch mit asm arbeite)? Und wann ist es angebracht stdcall zu verwenden? Finde häufig folgende Funktion in Quellcoden:

    extern int __stdcall foo(char*,int);
    

    .

    Hoffe jemand kann mir dazu mal nähere Informationen auch in Bezug auf Stackaufbau geben kann. Zum Stackaufbau von stdcall-Funktionen habe ich folgendes gefunden:

    Rest vom Stack
    Param2
    Param1
    Rücksprungaddresse
    saved ebp <-- ebp
    lokale Variable 1
    lokale Variable 2
    ...
    lokale Variable n
    saved ebx
    saved esi
    saved edi <-- esp

    Vielen Dank
    mirrowwinger



  • Erster Link zu google "stdcall": http://de.wikipedia.org/wiki/Aufrufkonvention
    Da klickt man dann wie bei allen Informatikthemen links auf "Englisch".
    http://en.wikipedia.org/wiki/X86_calling_conventions

    Rest vom Stack

    Dein Stack wächst nach unten? Krass.
    ebp, ebx, esi, edi mußte jeweils nur pushen, wenn Du sie selber verändern willst.



  • <a href= schrieb:

    http://gcc.godbolt.org/">

    testStdcall(int, int):
    	push	ebp
    	mov	ebp, esp
    	mov	edx, DWORD PTR [ebp+8]
    	mov	eax, DWORD PTR [ebp+12]
    	add	eax, edx
    	pop	ebp
    	ret	8
    
    testCdecl(int, int):
    	push	ebp
    	mov	ebp, esp
    	mov	edx, DWORD PTR [ebp+8]
    	mov	eax, DWORD PTR [ebp+12]
    	add	eax, edx
    	pop	ebp
    	ret
    
    main:
    	push	ebp
    	mov	ebp, esp
    	sub	esp, 16
    
    	push	3
    	push	1
    	call	testStdcall(int, int)
    	mov	DWORD PTR [ebp-4], eax
    
    	push	3
    	push	1
    	call	testCdecl(int, int)
    	add	esp, 8
    	mov	DWORD PTR [ebp-8], eax
    
    	mov	eax, 0
    	leave
    	ret
    

    Ich hab das ein bischen formatiert.
    Der wichtige Unterschied ist, wer den Speicher die Parameter anlegt/aufräumt.
    Zuerst werden zwei 4-Byte integer auf den Stack gepusht. Dann wird die Funktion aufgerufen.
    Bei stdcall ist das Funktionsende ein "ret 8", d.h. esp wird verschoben. Bei stdcall räumt also der Callee auf.
    Bei cdecl ist das Funktionende ein "ret", dafür kommt direkt nach dem Funktioncall ein "add esp, 8". Bei cdecl räumt also der Caller auf.

    Dadurch dass bei cdecl der Caller aufruft, werden z.B. variable Parameterlisten möglich. Bei stdcall geht das so nicht, da der Callee aufräumen müsste, wenn er nicht weiß wieviele Parameter er bekommt wird das schwer.

    Zur Verwendung der Callingconventions: Normal verwendet mal Cdecl (zumindest machen dass die meisten Compiler per default).
    Wenn du asm schreibst musst du halt drauf achten dass die Verwendung konsistent ist, wenn eine Funktion als cdecl aufgerufen wird sollte sie nicht den Stack aufräumen und andersrum. Wenn du asm schreibst würde ich mich einfach für eins entscheiden (je nach Anforderungen) und dann konsistent bleiben, dann kommst du schon nicht durcheinander.



  • First Things first:
    @DarkShadow44 danke, top Erklärung. Werde sicherlich noch den ein oder anderen Moment brauchen und auch das Beispiel richtig durcharbeiten müssen, um alles richtig zu verstehen. Aber danke für das ausführliche Beispiel.

    @volkard auch ein Danke, dass du entsprechende Wiki-Einträge für mich bereit gestellt hast. Aber auf dein Kommentar, dass mein Stack nach unten wächst möchte ich kurz eingehen. wie ich geschrieben hatte:

    Zum Stackaufbau von stdcall-Funktionen habe ich folgendes gefunden:

    hat, glaube ich, den aufmerksamen Leser suggeriert, dass MEIN STACK das wohl nicht sein kann. Ich schätze dein Wissen über den korrekten Aufbau des Speichermanagments. Deswegen möchte dir deswegen hiermit die Quelle auftun, aus der ich diesen Aufbau entnommen habe und überlasse dir MSDN darüber zu belehren, wie der Stack aufgebaut ist.
    http://blogs.msdn.com/b/oldnewthing/archive/2004/01/16/59415.aspx
    Für mich als Laie hat sich (vieleicht bin ich auch zu naiv) folgender Sinnvoller Aufbau des Stacks ergeben:

    HOHE SPEICHERADRESSE

    Rest vom Stack
    Param2
    Param1
    Rücksprungaddresse
    saved ebp <-- ebp
    lokale Variable 1
    lokale Variable 2
    ...
    lokale Variable n
    saved ebx
    saved esi
    saved edi <-- esp

    NIEDRIGE SPEICHERADDRESSE

    Der Stack wachst also immer noch von (optisch dargestellten) HOHEN Speicheradressen zu NIEDRIGEREN. Sicherlich muss man für die korrekte Darstellung noch um 180° drehen, aber ich hab mich nicht sonderlich daran gestört.

    mirrowwinger


Anmelden zum Antworten