Warum wird C / C ++ - Hauptargument als "char * argv []" deklariert und nicht nur als "char * argv"?

21

Warum wird es argvals "Zeiger auf Zeiger auf den ersten Index des Arrays" deklariert, anstatt nur "Zeiger auf den ersten Index des Arrays" ( char* argv) zu sein?

Warum ist hier der Begriff "Zeiger auf Zeiger" erforderlich?

Ein Benutzer
quelle
4
"Zeiger auf Zeiger auf den ersten Index des Arrays" - Das ist keine korrekte Beschreibung von char* argv[]oder char**. Das ist ein Zeiger auf einen Zeiger auf einen Charakter; Insbesondere zeigt der äußere Zeiger auf den ersten Zeiger in einem Array, und die inneren Zeiger zeigen auf die ersten Zeichen von Zeichenfolgen mit Nullen. Hier sind keine Indizes beteiligt.
Sebastian Redl
12
Wie würden Sie das zweite Argument erhalten, wenn es nur char * argv wäre?
gnasher729
15
Ihr Leben wird einfacher, wenn Sie den Raum an der richtigen Stelle platzieren. char* argv[]setzt den Raum an der falschen Stelle. Sagen wir char *argv[], und jetzt ist klar, dass dies "der Ausdruck *argv[n]ist eine Variable vom Typ char" bedeutet. Lassen Sie sich nicht darauf ein, herauszufinden, was ein Zeiger und was ein Zeiger auf einen Zeiger ist und so weiter. Die Deklaration sagt Ihnen, welche Operationen Sie mit diesem Ding ausführen können.
Eric Lippert
1
Vergleiche mental char * argv[]mit dem ähnlichen C ++ - Konstrukt std::string argv[], und es könnte einfacher sein, es zu analysieren. ... Schreib es einfach nicht so!
Justin Time 2 Setzen Sie Monica
2
@EricLippert beachte, dass die Frage auch C ++ enthält, und dort kannst du zB haben, char &func(int);was keinen &func(5)Typ hat char.
Ruslan

Antworten:

59

Argv sieht im Grunde so aus:

Bildbeschreibung hier eingeben

Links ist das Argument selbst - was eigentlich als Argument an main übergeben wird. Das enthält die Adresse eines Arrays von Zeigern. Jeder dieser Punkte verweist auf eine Stelle im Speicher, die den Text des entsprechenden Arguments enthält, das in der Befehlszeile übergeben wurde. Am Ende dieses Arrays wird dann garantiert ein Nullzeiger angezeigt.

Beachten Sie, dass der tatsächliche Speicher für die einzelnen Argumente zumindest potenziell separat voneinander zugewiesen wird, sodass ihre Adressen im Speicher möglicherweise ziemlich zufällig angeordnet sind (je nachdem, wie die Dinge geschrieben werden, können sie sich auch in einem zusammenhängenden Block von befinden Erinnerung - Sie wissen es einfach nicht und sollten sich nicht darum kümmern).

Jerry Sarg
quelle
52
Unabhängig davon, welche Layout-Engine das Diagramm für Sie erstellt hat, weist der Algorithmus zum Minimieren von Kreuzungen einen Fehler auf!
Eric Lippert
43
@EricLippert Könnte beabsichtigt sein, zu betonen, dass die Spitzen nicht zusammenhängend oder in Ordnung sind.
Jamesdlin
3
Ich würde sagen, es ist beabsichtigt
Michael
24
Es war sicherlich beabsichtigt - und ich würde vermuten, dass Eric das vermutet hat, aber (richtig, IMO) fand den Kommentar trotzdem lustig.
Jerry Coffin
2
@JerryCoffin, man könnte auch darauf hinweisen, dass selbst wenn die tatsächlichen Argumente im Speicher zusammenhängend sind, sie eine beliebige Länge haben können, so dass man immer noch unterschiedliche Zeiger für jeden von ihnen benötigt, um darauf zugreifen zu können, argv[i]ohne alle vorherigen zu durchsuchen.
Ilkkachu
22

Denn genau das bietet das Betriebssystem :-)

Ihre Frage ist ein kleines Problem mit der Henne / Ei-Inversion. Das Problem ist nicht zu wählen, was Sie in C ++ wollen, das Problem ist, wie Sie in C ++ sagen, was das Betriebssystem Ihnen gibt.

Unix übergibt ein Array von "Strings", wobei jeder String ein Befehlsargument ist. In C / C ++ ist eine Zeichenfolge ein "char *", sodass ein Array von Zeichenfolgen je nach Geschmack char * argv [] oder char ** argv ist.

Passant
quelle
13
Nein, es ist genau "das Problem zu wählen, was Sie in C ++ wollen". Beispielsweise stellt Windows die Befehlszeile als einzelne Zeichenfolge zur Verfügung, und C / C ++ - Programme erhalten dennoch ihr argvArray. Die Laufzeitumgebung übernimmt das Tokenisieren der Befehlszeile und das Erstellen des argvArrays beim Start.
Joker_vD
14
@ Joker_vD Ich denke verdreht, es geht darum, was das Betriebssystem dir gibt. Konkret: Ich denke, C ++ hat es so gemacht, weil C es so gemacht hat, und C hat es so gemacht, weil zu der Zeit C und Unix so untrennbar miteinander verbunden waren und Unix es so gemacht hat.
Daniel Wagner
1
@DanielWagner: Ja, dies ist aus dem Unix-Erbe von C. Unter Unix / Linux muss ein Minimum _start, das aufruft, mainlediglich maineinen Zeiger auf das vorhandene argvArray im Speicher übergeben. Es ist bereits im richtigen Format. Der Kernel kopiert es aus dem Argument argv in den execve(const char *filename, char *const argv[], char *const envp[])Systemaufruf, der zum Starten einer neuen ausführbaren Datei ausgeführt wurde. (Unter Linux befinden sich argv [] (das Array selbst) und argc beim Prozesseintrag auf dem Stack. Ich gehe davon aus, dass die meisten Unixe gleich sind, da dies ein guter Ort dafür ist.)
Peter Cordes
8
Joker meint hier jedoch, dass die C / C ++ - Standards es der Implementierung überlassen, woher die Argumente kommen. Sie müssen nicht direkt vom Betriebssystem stammen. Auf einem Betriebssystem, das eine flache Zeichenfolge übergibt, sollte eine gute C ++ - Implementierung das Tokenisieren einschließen, anstatt argc=2die gesamte flache Zeichenfolge festzulegen und zu übergeben. (Das Befolgen des Buchstabens des Standards ist nicht ausreichend, um nützlich zu sein . Es lässt absichtlich viel Raum für Implementierungsoptionen.) Obwohl einige Windows-Programme Anführungszeichen speziell behandeln möchten, bieten echte Implementierungen eine Möglichkeit, die flache Zeichenfolge zu erhalten. auch.
Peter Cordes
1
Basiles Antwort ist so ziemlich die + @ Joker-Korrektur und meine Kommentare mit mehr Details.
Peter Cordes
15

Erstens ist als Parameterdeklaration char **argvdasselbe wie char *argv[]; Beide implizieren einen Zeiger auf (ein Array oder eine Gruppe von einem oder mehreren möglichen) Zeiger auf Zeichenfolgen.

Wenn Sie nur "Zeiger auf Zeichen" haben - z. B. nur char *-, müssen Sie die ersten n-1 Elemente scannen, um den Start des n-ten Elements zu finden, um auf das n-te Element zuzugreifen. (Und dies würde auch die Anforderung auferlegen, dass jede der Zeichenfolgen zusammenhängend gespeichert wird.)

Mit dem Array von Zeigern können Sie das n-te Element direkt indizieren. Dies ist also (obwohl dies nicht unbedingt erforderlich ist - vorausgesetzt, die Zeichenfolgen sind zusammenhängend) im Allgemeinen viel praktischer.

Um zu veranschaulichen:

./programm hallo welt

argc = 3
argv[0] --> "./program\0"
argv[1] --> "hello\0"
argv[2] --> "world\0"

Es ist möglich, dass in einem von os bereitgestellten Array von Zeichen:

            "./program\0hello\0world\0"
argv[0]      ^
argv[1]                 ^
argv[2]                        ^

Wenn argv nur ein "Zeiger auf char" wäre, könnten Sie sehen

       "./program\0hello\0world\0"
argv    ^

Es gibt jedoch keine Garantie dafür, dass die drei Zeichenfolgen "./program", "hello" und "world" zusammenhängend sind. Ferner ist diese Art von "Einzelzeiger auf mehrere zusammenhängende Zeichenfolgen" ein ungewöhnlicheres Datentypkonstrukt (für C), insbesondere im Vergleich mit einem Array von Zeigern auf Zeichenfolgen.

Erik Eidt
quelle
was ist, wenn statt, argv --> "hello\0world\0"Sie haben argv --> index 0 of the array(hallo), genau wie ein normales Array. Warum ist das nicht machbar? dann liest du das array argcmal durch. dann übergeben Sie argv selbst und keinen Zeiger auf argv.
Ein Benutzer
@auser, das ist, was argv -> "./program\0hello\0\world\0" ist: ein Zeiger auf das erste Zeichen (dh das ".") Wenn Sie diesen Zeiger hinter dem ersten \ 0 nehmen, dann Sie habe einen Zeiger auf "Hallo \ 0" und danach auf "Welt \ 0". Nach einer kurzen Wartezeit (\ 0 ") sind Sie fertig. Sicher, es kann zum Laufen gebracht werden, und wie gesagt, ein ungewöhnliches Konstrukt.
Erik Eidt,
Sie haben vergessen , dass in Ihrem Beispiel zu erklären argv[4]istNULL
Basile Starynkevitch
3
Es gibt eine Garantie dafür (zumindest anfangs) argv[argc] == NULL. In diesem Fall ist das argv[3]nicht argv[4].
Miral
1
@Hill, ja, danke, da ich versucht habe, die Nullzeichen-Terminatoren explizit anzugeben (und diese verpasst habe).
Erik Eidt
13

Warum C / C ++ main argv als "char * argv []" deklariert wird

Eine mögliche Antwort ist, dass der C11-Standard n1570 (in §5.1.2.2.1 Programmstart ) und der C ++ 11-Standard n3337 (in §3.6.1 Hauptfunktion ) dies für gehostete Umgebungen erfordern (beachten Sie jedoch, dass der C-Standard dies erwähnt auch §5.1.2.1 freistehende Umgebungen ) Siehe auch dies .

Die nächste Frage ist, warum sich die C- und C ++ - Standards mainfür eine solche int main(int argc, char**argv)Signatur entschieden haben. Die Erklärung ist weitgehend historisch: C wurde mit Unix erfunden , das eine Shell hat, die vorher Globbing ausführt fork(das ist ein Systemaufruf zum Erstellen eines Prozesses) und execve(das ist der Systemaufruf zum Ausführen eines Programms) und das execveein Array überträgt von String-Programm-Argumenten und steht in Beziehung zu dem maindes ausgeführten Programms. Lesen Sie mehr über die Unix-Philosophie und über ABIs .

Und C ++ bemühte sich sehr, den Konventionen von C zu folgen und damit kompatibel zu sein. Es konnte nicht definiert werden main, dass es mit C-Traditionen unvereinbar ist.

Wenn Sie ein Betriebssystem von Grund auf neu entwickelt haben (noch mit einer Befehlszeilenschnittstelle) und eine Programmiersprache dafür von Grund auf neu entwickelt haben, können Sie verschiedene Programmstartkonventionen erfinden. Für andere Programmiersprachen (z. B. Common Lisp oder Ocaml oder Go) gelten andere Programmstartkonventionen.

In der Praxis mainwird von einem crt0- Code aufgerufen . Beachten Sie, dass das Globbing unter Windows von jedem Programm in der Entsprechung von crt0 ausgeführt werden kann und einige Windows-Programme über den nicht standardmäßigen WinMain-Einstiegspunkt gestartet werden können . Unter Unix wird das Globbing von der Shell ausgeführt (und crt0das ABI und das von ihr angegebene ursprüngliche Aufruflistenlayout werden an die Aufrufkonventionen Ihrer C-Implementierung angepasst).

Basile Starynkevitch
quelle
12

Anstatt es als "Zeiger auf Zeiger" zu betrachten, hilft es, es als "Array von Strings" zu betrachten, []wobei Array und char*String bezeichnet werden. Wenn Sie ein Programm ausführen, können Sie ihm ein oder mehrere Befehlszeilenargumente übergeben, die sich in den Argumenten für main: widerspiegeln. Dies argcist die Anzahl der Argumente und argvermöglicht den Zugriff auf einzelne Argumente.

casablanca
quelle
2
+1 Das! In vielen Sprachen - Bash, PHP, C, C ++ - ist argv ein Array von Strings. Daran musst du denken, wenn du char **oder siehst char *[], was dasselbe ist.
rexkogitans
1

In vielen Fällen lautet die Antwort "weil es ein Standard ist". Um den C99-Standard zu zitieren :

- Wenn der Wert von argc größer als Null ist, müssen die Array- Mitglieder argv [0] bis argv [argc-1] einschließlich Zeiger auf Zeichenfolgen enthalten , die vor dem Programmstart von der Host-Umgebung implementierungsdefinierte Werte erhalten.

Bevor es standardisiert wurde, wurde es von K & R C bereits in frühen Unix-Implementierungen zum Speichern von Befehlszeilenparametern verwendet (etwas, das Sie in der Unix-Shell beachten müssen, beispielsweise /bin/bashoder /bin/shnicht in eingebetteten Systemen). So zitieren Sie die erste Ausgabe von K & Rs "The C Programming Language" (S. 110) :

Das erste (üblicherweise als argc bezeichnet ) ist die Anzahl der Befehlszeilenargumente, mit denen das Programm aufgerufen wurde. Das zweite ( argv ) ist ein Zeiger auf ein Array von Zeichenfolgen, die die Argumente enthalten, eines pro Zeichenfolge.

Sergiy Kolodyazhnyy
quelle