Aufrufkonventionen


Was versteht man unter dem Begriff Aufrufkonventionen oder was ist damit gemeint? Wenn eine Funktion mit Parametern aufgerufen wird, werden die Parameter in der Regel auf dem Stack abgelegt. Bei einem Parameter ist das noch kein großes Problem, bei mehr als einem schon eher, denn der Aufrufer muss sich mit der aufzurufenden Funktion irgendwie abstimmen, in welcher Reihenfolge die Parameter auf dem Stack abgelegt werden. Ob nun der erste Parameter zuerst kommt oder der letzte. Ersteres bezeichnet man "von links nach rechts", zweiteres "von rechts nach links", wenn man sich vorstellt, dass die Parameter hintereinander stehen.

Gucken wir uns das mal auf der untersten Ebene in Assemblercode an. Wir wollen die API Funktion MessageBoxA aufrufen. Diese ist wie folgt deklariert:

int MessageBox(
  HWND hWnd,
  LPCTSTR lpText,
  LPCTSTR lpCaption,
  UINT uType
);

Als ersten Parameter erwartet die Funktion das Handle des aufrufenden Fensters, der zweite und dritte sind der Text und der Fenstertitel und der letzte definiert die Schaltflächen und das Icon, was angezeigt werden soll. Was für Code generiert nun der Compiler? Gucken wir mal:

push 0 ; uType
push offset Titel ; lpCaption
push offset Message ; lpText
push 0
call MessageBoxA

Mit push werden die erforderlichen Aufrufparameter auf den Stack geschoben. Und wie man sieht, geschieht das mit dem letzten zuerst, also wenn man sich die Liste der Parameter nebeneinander vorstellt, von rechts nach links. Dann wird mit call die Funktion selber aufgerufen. Sie findet ihre Parameter auf dem Stack, da wir sie genauso abgelegt haben, wie sie es erwartet. Kein Problem, funktioniert, alles prima. Funktion wird aufgerufen, die erwartete Messagebox erscheint, wir klicken sie weg und die Funktion kehrt zurück. So, danach kommt nichts mehr. Und warum kommt nichts mehr? Was ist mit dem Stack? Da liegen doch noch unsere Parameter rum. Nein tun sie nicht, denn die Funktion hat hinter uns hergeräumt und das Aufräumen für uns übernommen und den Stack wieder aufgeräumt. Das Ablegen von rechts nach links und dass die Funktion selber wieder aufräumt, wird mit der Aufrufkonvention stdcall vereinbart.

Was für Aufrufkonventionen gibt es noch? In der folgenden Tabelle habe ich mal die gebräuchlichen Aufrufkonventionen aufgelistet:

KonventionParameterreihenfolgeBereinigungParamterübergabe in Register
registerVon links nach rechtsRoutineJa
pascalVon links nach rechtsRoutineNein
cdeclVon rechts nach linksAufruferNein
stdcallVon rechts nach linksRoutineNein
safecallVon rechts nach linksRoutineNein

Zwei Aufrufkonventionen nehmen Sonderstellungen ein: register und stdcall. register deshalb, weil es die Standardaufrufkonvention von Delphi ist. Gibt man nichts anderes an, geht Delphi davon aus, dass man register meint. Diese Aufrufkonvention hat noch eine zweite Besonderheit. Sie legt die Parameter direkt in den Registern der CPU ab (Die ersten drei und was nicht mehr in die Register passt, wird auf den Stack gelegt.), was besonders effektiv ist, da sie dann der CPU direkt zur Verfügung stehen und nicht erst aus dem Arbeitsspeicher in die CPU-Register geladen werden müssen. Allerdings geht hier bei die Sprachunabhängigkeit verloren, da Delphi und C/C++ unterschiedliche Registeraufrufkonventionen haben. Die Übergabe von links nach rechts ist zu dem noch für den Parser / Compiler einfacher. Das stammt noch von Wirth selbst, der bei Pascal auch auf die Effizienz des Compilers geschaut hat.

Und stdcall ist insofern interessant, als dass es die Aufrufkonvention von Windows API Funktionen ist. Wenn also mal ein Funktionsaufruf nicht klappt und es sich um eine Windows API Funktion handelt, sollte man als erstes kontrollieren, ob man nicht die Aufrufkonvention vergessen hat und der Compiler, dumm wie er nun einmal ist, die Delphi-typische Aufrufkonvention register nimmt, was natürlich dazu führt, dass die Parameter falschherum auf dem Stack liegen.

Bei cedcl ist eine variable Anzahl Parameter möglicher, weshalb auch der Aufrufer die Parameter abräumen muss, da nur er weiß, wie viele Parameter er übergeben hat. Desweiteren müssen sie von rechts nach links auf dem Stack geschoben, damit sie dann aufsteigend auf dem Stack stehen, d. h. erster Parameter ist unten. Damit kann man in C (deshalb der Name cdecl) nun die Parameter als Array über einen Pointer zugreifen. Beispiels wiese geschiet das in der C-Funktion printf.

Mein Dank geht an Robert Marquardt für die zusätzlichen Informationen.


2010-12-29T23:44:37 +0100, mail+homepage[at]michael-puff.de