Pourquoi est-il argv
déclaré comme "un pointeur vers un pointeur vers le premier index du tableau", plutôt que d'être simplement "un pointeur vers le premier index du tableau" ( char* argv
)?
Pourquoi la notion de "pointeur à pointeur" est-elle requise ici?
char* argv[]
ouchar**
. C'est un pointeur vers un pointeur sur un personnage; en particulier, le pointeur externe pointe vers le premier pointeur d'un tableau et les pointeurs internes pointent vers les premiers caractères des chaînes terminées par zéro. Il n'y a aucun indice impliqué ici.char* argv[]
met l'espace au mauvais endroit. Disonschar *argv[]
, et maintenant il est clair que cela signifie "l'expression*argv[n]
est une variable de typechar
". Ne vous laissez pas prendre à essayer de déterminer ce qu'est un pointeur et ce qui est un pointeur vers un pointeur, etc. La déclaration vous indique quelles opérations vous pouvez effectuer sur cette chose.char * argv[]
à la construction C ++ similairestd::string argv[]
, et il pourrait être plus facile d'analyser. ... Ne commencez pas à l' écrire de cette façon!char &func(int);
qui ne fait pas&func(5)
avoir de typechar
.Réponses:
Argv est essentiellement comme ceci:
À gauche se trouve l'argument lui-même - ce qui est réellement passé comme argument à main. Cela contient l'adresse d'un tableau de pointeurs. Chacun de ces points pointe vers un endroit en mémoire contenant le texte de l'argument correspondant qui a été passé sur la ligne de commande. Ensuite, à la fin de ce tableau, il est garanti qu'il y a un pointeur nul.
Notez que le stockage réel pour les arguments individuels est au moins potentiellement alloué séparément les uns des autres, de sorte que leurs adresses en mémoire peuvent être organisées de manière assez aléatoire (mais selon la façon dont les choses se trouvent écrites, elles peuvent également être dans un seul bloc contigu de mémoire - vous ne savez tout simplement pas et ne devriez pas vous en soucier).
la source
argv[i]
sans parcourir tous les précédents.Parce que c'est ce que fournit le système d'exploitation :-)
Votre question est un peu un problème d'inversion poulet / œuf. Le problème n'est pas de choisir ce que vous voulez en C ++, le problème est de savoir comment vous dites en C ++ ce que le système d'exploitation vous donne.
Unix transmet un tableau de "chaînes", chaque chaîne étant un argument de commande. En C / C ++, une chaîne est un "char *", donc un tableau de chaînes est char * argv [], ou char ** argv, selon le goût.
la source
argv
tableau - le runtime se charge de tokeniser la ligne de commande et de construire leargv
tableau au démarrage._start
appelantmain
doit simplement passermain
un pointeur vers leargv
tableau existant en mémoire; c'est déjà dans le bon format. Le noyau le copie de l'argument argv vers l'execve(const char *filename, char *const argv[], char *const envp[])
appel système qui a été effectué pour démarrer un nouvel exécutable. (Sous Linux, argv [] (le tableau lui-même) et argc sont sur la pile à l'entrée de processus. Je suppose que la plupart des Unix sont les mêmes, car c'est un bon endroit pour cela.)argc=2
et de transmettre la chaîne plate entière. (Suivre la lettre de la norme n'est pas suffisant pour être utile ; cela laisse intentionnellement beaucoup de place aux choix d'implémentation.) Bien que certains programmes Windows veuillent traiter spécialement les guillemets, les implémentations réelles fournissent un moyen d'obtenir la chaîne plate, aussi.Tout d'abord, en tant que déclaration de paramètre,
char **argv
est identique àchar *argv[]
; ils impliquent tous deux un pointeur vers (un tableau ou un ensemble d'un ou plusieurs possibles) pointeur (s) vers des chaînes.Ensuite, si vous n'avez que "pointer to char" - par exemple juste
char *
- alors pour accéder au nième élément, vous devrez scanner les n-1 premiers éléments pour trouver le début du nième élément. (Et cela imposerait également l'exigence que chacune des chaînes soit stockée de manière contiguë.)Avec le tableau de pointeurs, vous pouvez directement indexer le nième élément - donc (bien que pas strictement nécessaire - en supposant que les chaînes sont contiguës), il est généralement beaucoup plus pratique.
Pour illustrer:
./programme bonjour le monde
Il est possible que, dans un tableau de caractères fourni par le système d'exploitation:
si argv n'était qu'un "pointeur sur char", vous pourriez voir
Cependant (bien que probable par la conception de l'os), il n'y a aucune garantie réelle que les trois chaînes "./program", "hello" et "world" sont contiguës. En outre, ce type de "pointeur unique vers plusieurs chaînes contiguës" est une construction de type de données plus inhabituelle (pour C), en particulier par rapport au tableau de pointeurs vers la chaîne.
la source
argv --> "hello\0world\0"
vous avezargv --> index 0 of the array
(bonjour), tout comme un tableau normal. pourquoi est-ce pas faisable? alors vous continuez à lire lesargc
heures du tableau . alors vous passez argv lui-même et non un pointeur sur argv.argv[4]
estNULL
argv[argc] == NULL
. Dans ce casargv[3]
, nonargv[4]
.Une réponse possible est que la norme C11 n1570 (au §5.1.2.2.1 Démarrage du programme ) et la norme C ++ 11 n3337 (au §3.6.1 fonction principale ) l' exigent pour les environnements hébergés (mais notez que la norme C mentionne également §5.1.2.1 environnements autonomes ) Voir aussi ceci .
La question suivante est pourquoi les normes C et C ++ ont-elles choisi
main
d'avoir une telleint main(int argc, char**argv)
signature? L'explication est largement historique: C a été inventé avec Unix , qui a un shell qui fait un globbing avant de fairefork
(qui est un appel système pour créer un processus) etexecve
(qui est l'appel système pour exécuter un programme), et quiexecve
transmet un tableau des arguments de programme de chaîne et est lié à celuimain
du programme exécuté. En savoir plus sur la philosophie Unix et sur les ABI .Et C ++ s'est efforcé de suivre les conventions de C et d'être compatible avec lui. Il ne pouvait pas définir
main
l'incompatibilité avec les traditions C.Si vous avez conçu un système d'exploitation à partir de zéro (ayant toujours une interface de ligne de commande) et un langage de programmation pour lui à partir de zéro, vous serez libre d'inventer différentes conventions de démarrage de programme. Et d'autres langages de programmation (par exemple Common Lisp ou Ocaml ou Go) ont des conventions de démarrage de programme différentes.
En pratique,
main
est invoqué par un code crt0 . Notez que sous Windows, la globalisation peut être effectuée par chaque programme dans l'équivalent de crt0, et certains programmes Windows peuvent démarrer via le point d'entrée WinMain non standard . Sous Unix, la globalisation est effectuée par le shell (etcrt0
adapte l'ABI, et la disposition initiale de la pile d'appels qu'il a spécifiée, aux conventions d'appel de votre implémentation C).la source
Plutôt que de le considérer comme un "pointeur à pointeur", il est utile de le considérer comme un "tableau de chaînes", avec
[]
dénotation tableau etchar*
dénotation chaîne. Lorsque vous exécutez un programme, vous pouvez lui passer un ou plusieurs arguments de ligne de commande et ceux-ci sont reflétés dans les arguments àmain
:argc
est le nombre d'arguments etargv
vous permet d'accéder aux arguments individuels.la source
char **
ouchar *[]
, ce qui est le même.Dans de nombreux cas, la réponse est "parce que c'est une norme". Pour citer la norme C99 :
Bien sûr, avant qu'il ne soit normalisé, il était déjà utilisé par K&R C dans les premières implémentations Unix, dans le but de stocker des paramètres de ligne de commande (quelque chose dont vous devez vous soucier dans le shell Unix, comme
/bin/bash
ou/bin/sh
pas dans les systèmes embarqués). Pour citer la première édition du «langage de programmation C» de K&R (p. 110) :la source