Perché C / C ++ main argv viene dichiarato come "char * argv []" anziché semplicemente "char * argv"?


21

Perché viene argvdichiarato come "un puntatore al puntatore al primo indice dell'array", anziché essere semplicemente "un puntatore al primo indice dell'array" ( char* argv)?

Perché qui è richiesta la nozione di "puntatore a puntatore"?


4
"un puntatore al puntatore al primo indice dell'array" - Questa non è una descrizione corretta di char* argv[]o char**. Questo è un puntatore a un puntatore a un personaggio; in particolare il puntatore esterno punta al primo puntatore in un array, mentre i puntatori interni puntano ai primi caratteri delle stringhe nul-terminate. Non ci sono indici coinvolti qui.
Sebastian Redl,

12
Come otterresti il ​​secondo argomento se fosse solo char * argv?
gnasher729,

15
La tua vita diventerà più facile quando metterai lo spazio nel posto giusto. char* argv[]mette lo spazio nel posto sbagliato. Dì char *argv[], e ora è chiaro che questo significa "l'espressione *argv[n]è una variabile di tipo char". Non lasciarti sorprendere dal tentativo di capire cos'è un puntatore, cos'è un puntatore a un puntatore e così via. La dichiarazione ti dice quali operazioni puoi eseguire su questa cosa.
Eric Lippert,

1
Confronta mentalmente char * argv[]con il costrutto C ++ simile std::string argv[]e potrebbe essere più semplice analizzarlo. ... Non iniziare a scriverlo in quel modo!
Justin Time 2 Ripristina Monica il

2
@EricLippert nota che la domanda include anche C ++, e lì puoi avere, ad esempio, char &func(int);che non &func(5)ha tipo char.
Ruslan,

Risposte:


59

Argv è sostanzialmente così:

inserisci qui la descrizione dell'immagine

A sinistra è l'argomento stesso - ciò che è effettivamente passato come argomento a main. Ciò contiene l'indirizzo di una matrice di puntatori. Ognuno di questi punti in un punto della memoria contenente il testo dell'argomento corrispondente che è stato passato sulla riga di comando. Quindi, alla fine di quell'array, è garantito che sia un puntatore nullo.

Si noti che l'archiviazione effettiva per i singoli argomenti è almeno potenzialmente allocata separatamente l'una dall'altra, quindi i loro indirizzi in memoria potrebbero essere disposti in modo abbastanza casuale (ma a seconda di come le cose accadono per essere scritte, potrebbero anche essere in un singolo blocco contiguo di memoria - semplicemente non lo sai e non ti dovrebbe interessare).


52
Qualunque sia il motore di layout che ha disegnato quel diagramma per te ha un bug nel loro algoritmo di minimizzazione dei passaggi!
Eric Lippert,

43
@EricLippert Potrebbe essere intenzionale sottolineare che le punte potrebbero non essere contigue né in ordine.
Jamesdlin,

3
Direi che è intenzionale
Michael

24
Era certamente intenzionale - e immagino che probabilmente Eric lo avesse capito, ma (correttamente, IMO) pensava che il commento fosse comunque divertente.
Jerry Coffin, il

2
@JerryCoffin, si potrebbe anche sottolineare che anche se gli argomenti reali fossero contigui in memoria, possono avere lunghezze arbitrarie, quindi si avrebbero comunque bisogno di puntatori distinti per ciascuno di essi per poter accedere argv[i]senza scorrere tutti quelli precedenti.
ilkkachu,

22

Perché è quello che fornisce il sistema operativo :-)

La tua domanda è un po 'un problema di inversione di pollo / uovo. Il problema non è scegliere quello che vuoi in C ++, il problema è come dici in C ++ cosa ti dà il sistema operativo.

Unix passa una matrice di "stringhe", ogni stringa è un argomento di comando. In C / C ++, una stringa è un "char *", quindi un array di stringhe è char * argv [] o char ** argv, secondo i gusti.


13
No, è esattamente "il problema di scegliere quello che vuoi in C ++". Windows, ad esempio, fornisce la riga di comando come una singola stringa, eppure i programmi C / C ++ ricevono ancora il loro argvarray - il runtime si occupa di tokenizzare la riga di comando e di costruire l' argvarray all'avvio.
Joker_vD

14
@Joker_vD Penso che in un modo contorto che è su ciò che il sistema operativo che si dà. In particolare: immagino che C ++ lo abbia fatto in questo modo perché C lo ha fatto in questo modo, e C lo ha fatto in questo modo perché all'epoca C e Unix erano così indissolubilmente collegati e Unix lo ha fatto in questo modo.
Daniel Wagner,

1
@DanielWagner: Sì, questo proviene dall'eredità Unix di C. Su Unix / Linux un minimo _startche chiama maindeve solo passare mainun puntatore argvall'array esistente in memoria; è già nel formato giusto. Il kernel lo copia dall'argomento argv alla execve(const char *filename, char *const argv[], char *const envp[])chiamata di sistema che è stata fatta per avviare un nuovo eseguibile. (Su Linux, argv [] (l'array stesso) e argc sono in pila all'ingresso del processo. Presumo che la maggior parte degli Unix siano gli stessi, perché è un buon posto per questo.)
Peter Cordes

8
Ma il punto di Joker qui è che gli standard C / C ++ lasciano fino all'implementazione da cui provengono gli arg; non devono essere direttamente dal sistema operativo. Su un sistema operativo che passa una stringa piatta, una buona implementazione C ++ dovrebbe includere la tokenizzazione, invece di impostare argc=2e passare l'intera stringa piatta. (Seguire la lettera dello standard non è sufficiente per essere utile ; lascia intenzionalmente molto spazio per le scelte di implementazione.) Sebbene alcuni programmi Windows vorranno trattare le virgolette appositamente, quindi le implementazioni reali forniscono un modo per ottenere la stringa piatta, pure.
Peter Cordes,

1
La risposta di Basile è praticamente questa correzione di + @ Joker e i miei commenti, con maggiori dettagli.
Peter Cordes,

15

Innanzitutto, come dichiarazione di parametro, char **argvè la stessa di char *argv[]; entrambi implicano un puntatore a (un array o un insieme di uno o più possibili) puntatore / i alle stringhe.

Successivamente, se hai solo "puntatore al carattere" - ad es. Solo char *- quindi per accedere all'ennesimo elemento, dovrai scansionare i primi n-1 elementi per trovare l'inizio dell'ennesimo elemento. (E questo imporrebbe anche il requisito che ciascuna stringa sia memorizzata in modo contiguo.)

Con la matrice di puntatori, puoi indicizzare direttamente l'ennesimo elemento - quindi (sebbene non strettamente necessario - supponendo che le stringhe siano contigue) è generalmente molto più conveniente.

Illustrare:

./programma ciao mondo

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

È possibile che, in un sistema operativo fornito, array di caratteri:

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

se argv fosse solo un "puntatore al carattere" potresti vedere

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

Tuttavia (sebbene probabilmente in base alla progettazione del sistema operativo) non vi è alcuna reale garanzia che le tre stringhe "./program", "hello" e "world" siano contigue. Inoltre, questo tipo di "puntatore singolo a più stringhe contigue" è un costrutto di tipo di dati più insolito (per C), soprattutto rispetto alla matrice di puntatori a stringa.


e se invece di, argv --> "hello\0world\0"hai argv --> index 0 of the array(ciao), proprio come un normale array. perché non è fattibile? quindi continui a leggere i argctempi dell'array . poi passi argv stesso e non un puntatore ad argv.
un utente il

@utente, ecco cosa argv -> "./program\0hello\0\world\0" è: un puntatore al primo carattere (cioè il ".") Se porti quel puntatore oltre il primo \ 0, allora avere un puntatore a "ciao \ 0", e successivamente a "mondo \ 0". Dopo i tempi argc (colpendo \ 0 "), il gioco è fatto. Certo, può essere fatto funzionare, e come ho detto, un costrutto insolito.
Erik Eidt

Hai dimenticato di affermare che nel tuo esempio argv[4]èNULL
Basile Starynkevitch il

3
C'è una garanzia che (almeno inizialmente) argv[argc] == NULL. In questo caso argv[3]no argv[4].
Miral,

1
@ Ciao, grazie, mentre stavo cercando di essere esplicito sui terminatori di caratteri null (e ho perso quello).
Erik Eidt,

13

Perché C / C ++ main argv è dichiarato come "char * argv []"

Una possibile risposta è perché lo standard C11 n1570 (in §5.1.2.2.1 Avvio del programma ) e lo standard C ++ 11 n3337 (in §3.6.1 funzione principale ) richiedono che per gli ambienti ospitati (ma si noti che lo standard C menziona anche §5.1.2.1 ambienti indipendenti ) Vedi anche questo .

La domanda successiva è: perché gli standard C e C ++ hanno scelto maindi avere una simile int main(int argc, char**argv)firma? La spiegazione è in gran parte storica: C è stata inventata con Unix , che ha una shell che fa globbing prima di fare fork(che è una chiamata di sistema per creare un processo) e execve(che è la chiamata di sistema per eseguire un programma), e che execvetrasmette un array degli argomenti del programma di stringa ed è correlato al mainprogramma eseguito. Leggi di più sulla filosofia Unix e sulle ABI .

E C ++ si è sforzato di seguire le convenzioni di C e di essere compatibile con esso. Non si potrebbe definire mainincompatibile con le tradizioni C.

Se hai progettato un sistema operativo da zero (pur avendo un'interfaccia a riga di comando) e un linguaggio di programmazione da zero, sarai libero di inventare diverse convenzioni di avvio del programma. E altri linguaggi di programmazione (ad esempio Common Lisp o Ocaml o Go) hanno convenzioni di avvio del programma diverse.

In pratica, mainviene invocato da un codice crt0 . Si noti che su Windows il globbing può essere eseguito da ciascun programma nell'equivalente di crt0 e alcuni programmi Windows possono iniziare tramite il punto di ingresso WinMain non standard . Su Unix, il globbing viene eseguito dalla shell (e crt0sta adattando l'ABI e il layout dello stack di chiamate iniziale che ha specificato, alle convenzioni di chiamata dell'implementazione C).


12

Invece di pensarlo come "puntatore a puntatore", aiuta a pensarlo come "array di stringhe", con []array che char*indica e che indica stringa. Quando si esegue un programma, è possibile passare uno o più argomenti della riga di comando e questi si riflettono negli argomenti a main: argcè il conteggio degli argomenti e argvconsente di accedere a singoli argomenti.


2
+1 questo! In molte lingue - bash, PHP, C, C ++ - argv è una matrice di stringhe. Di questo devi pensare quando vedi char **o char *[], che è lo stesso.
rexkogitans,

1

In molti casi la risposta è "perché è uno standard". Per citare lo standard C99 :

- Se il valore di argc è maggiore di zero, i membri dell'array argv [0] tramite argv [argc-1] inclusivo devono contenere puntatori alle stringhe , a cui vengono forniti valori definiti dall'implementazione dall'ambiente host prima dell'avvio del programma.

Naturalmente, prima che fosse standardizzato, era già in uso da K&R C nelle prime implementazioni di Unix, con lo scopo di memorizzare i parametri della riga di comando (qualcosa che devi preoccuparti nella shell Unix come /bin/basho /bin/shma non nei sistemi embedded). Per citare la prima edizione di "The C Programming Language" di K&R (pag. 110) :

Il primo (convenzionalmente chiamato argc ) è il numero di argomenti della riga di comando con cui è stato invocato il programma; il secondo ( argv ) è un puntatore a un array di stringhe di caratteri che contengono gli argomenti, uno per stringa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.