Warum sind die Adressen von argc und argv 12 Bytes voneinander entfernt?

40

Ich habe das folgende Programm auf meinem Computer ausgeführt (64-Bit-Intel unter Linux).

#include <stdio.h>

void test(int argc, char **argv) {
    printf("[test] Argc Pointer: %p\n", &argc);
    printf("[test] Argv Pointer: %p\n", &argv);
}

int main(int argc, char **argv) {
    printf("Argc Pointer: %p\n", &argc);
    printf("Argv Pointer: %p\n", &argv);
    printf("Size of &argc: %lu\n", sizeof (&argc));
    printf("Size of &argv: %lu\n", sizeof (&argv));
    test(argc, argv);
    return 0;
}

Die Ausgabe des Programms war

$ gcc size.c -o size
$ ./size
Argc Pointer: 0x7fffd7000e4c
Argv Pointer: 0x7fffd7000e40
Size of &argc: 8
Size of &argv: 8
[test] Argc Pointer: 0x7fffd7000e2c
[test] Argv Pointer: 0x7fffd7000e20

Die Größe des Zeigers &argvbeträgt 8 Bytes. Ich habe erwartet, dass die Adresse von lautet argc, address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8aber dazwischen befindet sich ein 4-Byte-Abstand. Warum ist das so?

Ich vermute, dass es an der Speicherausrichtung liegen könnte, bin mir aber nicht sicher.

Ich bemerke das gleiche Verhalten auch bei den Funktionen, die ich aufrufe.

letmutx
quelle
15
Warum nicht? Sie könnten 174 Bytes voneinander entfernt sein. Eine Antwort hängt von Ihrem Betriebssystem und / oder einer Wrapper-Bibliothek ab, für die das Setup durchgeführt wird main.
Aschepler
2
@aschepler: Es sollte nicht von einem Wrapper abhängen, für den das Setup durchgeführt wird main. In C mainkann als reguläre Funktion aufgerufen werden, daher muss es Argumente wie eine reguläre Funktion empfangen und dem ABI gehorchen.
Eric Postpischil
@aschelper: Ich bemerke das gleiche Verhalten auch für andere Funktionen.
Letmutx
4
Es ist ein interessantes "Gedankenexperiment", aber eigentlich gibt es nichts, was mehr sein sollte als ein "Ich frage mich warum". Diese Adressen können sich je nach Betriebssystem, Compiler, Compilerversion und Prozessorarchitektur ändern und sollten im „echten Leben“ in keiner Weise davon abhängen.
Neil
2
Das Ergebnis von sizeof muss mit%zu
phuclv

Antworten:

61

Auf Ihrem System werden die ersten paar Ganzzahl- oder Zeigerargumente in Registern übergeben und haben keine Adressen. Wenn Sie ihre Adressen mit &argcoder nehmen &argv, muss der Compiler Adressen fabrizieren, indem er den Registerinhalt in Stapelpositionen schreibt und Ihnen die Adressen dieser Stapelpositionen gibt. Dabei wählt der Compiler gewissermaßen die Stapelpositionen aus, die für ihn geeignet sind.

Eric Postpischil
quelle
6
Beachten Sie, dass dies auch dann passieren kann , wenn sie auf dem Stapel übergeben werden . Der Compiler ist nicht verpflichtet, den Slot für eingehende Werte auf dem Stapel als Speicher für die lokalen Objekte zu verwenden, in die die Werte gehen. Dies ist möglicherweise sinnvoll, da die Funktion möglicherweise einen Tail-Call ausführt und die aktuellen Werte dieser Objekte benötigt, um die ausgehenden Argumente für den Tail-Call zu erzeugen.
R .. GitHub STOP HELPING ICE
10

Warum sind die Adressen von argc und argv 12 Bytes voneinander entfernt?

Aus Sicht des Sprachstandards lautet die Antwort "kein besonderer Grund". C spezifiziert oder impliziert keine Beziehung zwischen den Adressen von Funktionsparametern. @EricPostpischil beschreibt, was wahrscheinlich in Ihrer speziellen Implementierung passiert, aber diese Details wären für eine Implementierung unterschiedlich, bei der alle Argumente auf dem Stapel übergeben werden, und dies ist nicht die einzige Alternative.

Außerdem habe ich Probleme, eine Möglichkeit zu finden, wie solche Informationen innerhalb eines Programms nützlich sein können. Selbst wenn Sie beispielsweise "wissen", dass die Adresse von argv12 Byte vor der Adresse von liegt argc, gibt es immer noch keine definierte Möglichkeit, einen dieser Zeiger aus dem anderen zu berechnen.

John Bollinger
quelle
7
@ R..GitHubSTOPHELPINGICE: Das Berechnen voneinander ist teilweise definiert, nicht gut definiert. Der C-Standard ist nicht streng in uintptr_tBezug auf die Durchführung der Konvertierung in und definiert sicherlich keine Beziehungen zwischen den Adressen von Parametern oder wo Argumente übergeben werden.
Eric Postpischil
6
@ R. -Zeiger. Mathematisch und logisch bedeutet dies nicht, dass g (f (x) +4) = x + 4 ist. Wenn zum Beispiel f (x) x² und g (y) sqrt (y) wären, dann wäre g (f (x)) = x (für reales nicht negatives x), aber g (f (x) +4) ≠ x + 4 im Allgemeinen. Im Fall von Zeigern kann die Konvertierung in uintptr_teine Adresse in den hohen 24 Bits und einige Authentifizierungsbits in den niedrigen 8 Bits ergeben. Wenn Sie dann 4 hinzufügen, wird die Authentifizierung nur noch fehlerhafter. es wird nicht aktualisiert…
Eric Postpischil
5
… Die Adressbits. Oder die Konvertierung in uintptr_t könnte eine Basisadresse in den hohen 16 Bits und einen Offset in den niedrigen 16 Bits ergeben, und das Hinzufügen von 4 zu den niedrigen Bits könnte in die hohen Bits übertragen werden, aber die Skalierung ist falsch (weil die dargestellte Adresse nicht ist Basis • 65536 + Offset, sondern Basis • 64 + Offset, wie es in einigen Systemen der Fall war). Ganz einfach, das, was uintptr_tSie aus einer Conversion erhalten, ist nicht unbedingt eine einfache Adresse.
Eric Postpischil
4
@ R..GitHubSTOPHELPINGICE Aus meiner Lektüre des Standards geht hervor, dass es nur eine schwache Garantie gibt, (void *)(uintptr_t)(void *)pdie mit gleich vergleichbar ist (void *)p. Es ist anzumerken, dass das Komitee fast genau dieses Thema kommentiert hat und zu dem Schluss gekommen ist, dass "Implementierungen ... Zeiger, die auf unterschiedlichen Ursprüngen basieren, auch als unterschiedlich behandeln können, obwohl sie bitweise identisch sind ".
Ryan Avella
5
@ R..GitHubSTOPHELPINGICE: Entschuldigung, ich habe übersehen, dass Sie einen Wert hinzugefügt haben, der als Unterschied zwischen zwei uintptr_tKonvertierungen von Adressen und nicht als Unterschied zwischen Zeigern oder einer „bekannten“ Entfernung in Bytes berechnet wurde . Sicher, das stimmt, aber wie ist es nützlich? Es bleibt wahr, dass "es immer noch keine definierte Möglichkeit gibt, einen dieser Zeiger aus dem anderen zu berechnen", wie in der Antwort angegeben, aber diese Berechnung berechnet nicht baus a, sondern berechnet baus beiden aund b, da bin der Subtraktion verwendet werden muss, um den Betrag zu berechnen hinzufügen. Das Berechnen voneinander ist nicht definiert.
Eric Postpischil