Wie können variadische Funktionen wie printf die Anzahl der Argumente herausfinden, die sie bekommen haben?
Die Menge der Argumente wird offensichtlich nicht als (versteckter) Parameter übergeben (siehe a Rufen Sie hier printf in asm auf.
Was ist der Trick?
Der Trick ist, dass Sie ihnen irgendwie anders erzählen. Für printf
müssen Sie eine Format-Zeichenfolge angeben, die sogar Typinformationen enthält (die jedoch möglicherweise nicht korrekt sind). Die Art und Weise, diese Informationen zu liefern, ist hauptsächlich benutzervertraglich und oft fehleranfällig.
Wie beim Aufruf von Conventions: Normalerweise werden die Argumente von links nach rechts auf den Stack geschoben und dann die Backjump-Adresse. Die aufrufende Routine löscht den Stapel. Es besteht also keine technische Notwendigkeit, dass die aufgerufene Routine die Anzahl der Parameter kennt.
EDIT: In C ++ 0x gibt es einen sicheren Weg (sogar typsicher!), um variadische Funktionen aufzurufen!
Implizit aus der Formatzeichenfolge. Beachten Sie, dass stdarg.h keine Makros enthält, um die Gesamtzahl der übergebenen Argumente abzurufen. Dies ist auch einer der Gründe, warum die C-Aufrufkonvention verlangt, dass der Aufrufer den Stack bereinigt, obwohl dies die Codegröße erhöht.
Aus diesem Grund werden Argumente in der C-Aufrufkonvention in umgekehrter Reihenfolge übergeben, z. B .:
Wenn Sie anrufen:
%Vor%Der Stapel endet wie folgt:
%Vor%Argumente werden indirekt unter Verwendung ihres Offsets vom Rahmenzeiger (der Rahmenzeiger kann von intelligenten Compilern weggelassen werden, die wissen, wie man Dinge vom Stapelzeiger berechnet). Das erste Argument befindet sich immer an einer bekannten Adresse in diesem Schema. Die Funktion greift auf so viele Argumente zu, wie es ihren ersten Argumenten sagt.
Versuchen Sie Folgendes:
%Vor%Dadurch wird ein Teil des Stapels gelöscht.
Der AMD64 System V ABI (Linux, Mac OS X) übergibt den Zahlenvektor (SEE / AVX) varargs in rax
, im Gegensatz zu IA-32. Siehe auch: Warum wird% eax vor einem Aufruf von printf auf Null gesetzt?
TODO warum ist das erforderlich? Ich denke, es ist nur aus Performance-Gründen, nicht unnötige SSE-Register in der "Register Save Area" zu speichern, die in "3.5.7 Variable Argument Lists" erwähnt wird.
Auf der C-Ebene gibt es auch andere Techniken als das Parsen der Formatzeichenfolge, wie von anderen erwähnt. Sie könnten auch:
Übergeben Sie ein Sentinel (void *)0
, um das letzte Argument wie execl anzugeben.
Sie möchten das Funktionsattribut sentinel
verwenden, um GCC bei der Kompilierung zu unterstützen: C Warnung Fehlender Sentinel im Funktionsaufruf
Übergeben Sie es als extra Integer Argument mit der Anzahl der Varargs
Verwenden Sie das Attribut format
function, um GCC bei der Durchsetzung von Formatstrings bekannter Typen wie printf
oder strftime