Dois-je utiliser char ** argv ou char * argv []?


125

J'apprends juste C et je me demande lequel de ceux-ci je devrais utiliser dans ma méthode principale. Y a-t-il une différence? Lequel est le plus courant?


2
Je préfère 'char ** argv', donc je le vois plus souvent, mais les deux sont corrects et je ne changerais pas une déclaration simplement parce qu'elle était écrite 'char * argv []'.
Jonathan Leffler

+1 parce que les problèmes plus profonds du tableau par rapport au pointeur sont importants à bien comprendre.
RBerteig

2
Vraiment, très bonne question. Je vous remercie.
Frank V

5
Lisez la section 6 de la FAQ comp.lang.c ; c'est la meilleure explication de la relation entre les tableaux C et les pointeurs que j'ai vue. Deux points importants: 1. char **argvéquivaut exactement à char *argv[]une déclaration de paramètre (et uniquement à une déclaration de paramètre). 2. les tableaux ne sont pas des pointeurs.
Keith Thompson

Réponses:


160

Comme vous venez d'apprendre C, je vous recommande d'essayer vraiment de comprendre les différences entre les tableaux et les pointeurs d'abord plutôt que les choses courantes .

Dans le domaine des paramètres et des tableaux, il existe quelques règles déroutantes qui devraient être claires avant de continuer. Tout d'abord, ce que vous déclarez dans une liste de paramètres est traité de manière spéciale. Il y a de telles situations où les choses n'ont pas de sens en tant que paramètre de fonction en C. Ce sont

  • Fonctions comme paramètres
  • Tableaux en tant que paramètres

Tableaux en tant que paramètres

Le second n'est peut-être pas immédiatement clair. Mais cela devient clair quand on considère que la taille d'une dimension de tableau fait partie du type en C (et un tableau dont la taille de dimension n'est pas donnée a un type incomplet). Donc, si vous créez une fonction qui prend un tableau par valeur (reçoit une copie), alors elle ne peut le faire que pour une taille! De plus, les tableaux peuvent devenir volumineux et C essaie d'être aussi rapide que possible.

En C, pour ces raisons, les valeurs de tableau n'existent pas. Si vous souhaitez obtenir la valeur d'un tableau, vous obtenez à la place un pointeur vers le premier élément de ce tableau. Et c'est là que réside déjà la solution. Au lieu de dessiner un paramètre de tableau invalide à l'avance, un compilateur C transformera le type du paramètre respectif en un pointeur. Rappelez-vous ceci, c'est très important. Le paramètre ne sera pas un tableau, mais plutôt un pointeur vers le type d'élément respectif.

Maintenant, si vous essayez de passer un tableau, ce qui est passé à la place est un pointeur vers le premier élément des tableaux.

Excursion: fonctionne comme paramètres

Pour terminer, et parce que je pense que cela vous aidera à mieux comprendre la question, regardons quel est l'état des choses lorsque vous essayez d'avoir une fonction comme paramètre. En effet, d'abord cela n'aura aucun sens. Comment un paramètre peut-il être une fonction? Huh, nous voulons une variable à cet endroit, bien sûr! Donc, ce que fait le compilateur lorsque cela se produit, c'est encore une fois de transformer la fonction en un pointeur de fonction . Essayer de passer une fonction passera un pointeur vers cette fonction respective à la place. Ainsi, les éléments suivants sont identiques (analogues à l'exemple de tableau):

void f(void g(void));
void f(void (*g)(void));

Notez que les parenthèses autour *gsont nécessaires. Sinon, il spécifierait une fonction retournant void*, au lieu d'un pointeur vers une fonction retournant void.

Retour aux tableaux

Maintenant, j'ai dit au début que les tableaux peuvent avoir un type incomplet - ce qui arrive si vous ne donnez pas encore de taille. Puisque nous avons déjà compris qu'un paramètre de tableau n'existe pas mais qu'à la place tout paramètre de tableau est un pointeur, la taille du tableau n'a pas d'importance. Cela signifie que le compilateur traduira tous les éléments suivants, et tous sont la même chose:

int main(int c, char **argv);
int main(int c, char *argv[]);
int main(int c, char *argv[1]);
int main(int c, char *argv[42]);

Bien sûr, cela n'a pas beaucoup de sens de pouvoir y mettre n'importe quelle taille, et c'est juste jeté. Pour cette raison, C99 a proposé une nouvelle signification pour ces nombres et permet à d'autres choses d'apparaître entre les crochets:

// says: argv is a non-null pointer pointing to at least 5 char*'s
// allows CPU to pre-load some memory. 
int main(int c, char *argv[static 5]);

// says: argv is a constant pointer pointing to a char*
int main(int c, char *argv[const]);

// says the same as the previous one
int main(int c, char ** const argv);

Les deux dernières lignes indiquent que vous ne pourrez pas changer "argv" dans la fonction - c'est devenu un pointeur const. Cependant, seuls quelques compilateurs C prennent en charge ces fonctionnalités C99. Mais ces caractéristiques montrent clairement que le «tableau» n'en est pas un. C'est un indicateur.

Un mot d'avertissement

Notez que tout ce que j'ai dit ci-dessus n'est vrai que lorsque vous avez un tableau en tant que paramètre d'une fonction. Si vous travaillez avec des tableaux locaux, un tableau ne sera pas un pointeur. Il se comportera comme un pointeur, car comme expliqué précédemment, un tableau sera converti en pointeur lors de la lecture de sa valeur. Mais il ne doit pas être confondu avec des pointeurs.

Un exemple classique est le suivant:

char c[10]; 
char **c = &c; // does not work.

typedef char array[10];
array *pc = &c; // *does* work.

// same without typedef. Parens needed, because [...] has 
// higher precedence than '*'. Analogous to the function example above.
char (*array)[10] = &c;

2
Je n'avais aucune idée que celui-ci existe: * argv [statique 1]
superlukas

12

Vous pouvez utiliser l'un ou l'autre. Ils sont complètement équivalents. Voir les commentaires de litb et sa réponse .

Cela dépend vraiment de la façon dont vous voulez l'utiliser (et vous pouvez utiliser l'un ou l'autre dans tous les cas):

// echo-with-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char **argv)
{
  while (--argc > 0)
  {
    printf("%s ", *++argv);
  }
  printf("\n");
  return 0;
}

// echo-without-pointer-arithmetic.c
#include <stdio.h>
int main(int argc, char *argv[])
{
  int i;
  for (i=1; i<argc; i++)
  {
    printf("%s ", argv[i]);
  }
  printf("\n");
  return 0;
}

Quant à savoir ce qui est le plus courant, cela n'a pas d'importance. Tout programmeur C expérimenté lisant votre code verra les deux comme interchangeables (dans les bonnes conditions). Tout comme un anglophone expérimenté lit «ils» et «ils sont» tout aussi facilement.

Le plus important est que vous appreniez à les lire et à reconnaître à quel point ils sont similaires. Vous lirez plus de code que vous n'en écrivez et vous devrez être tout aussi à l'aise avec les deux.


7
char * argv [] équivaut à 100% à char ** argv lorsqu'il est utilisé comme type de paramètre d'une fonction. pas de "const" impliqué, pas non plus implicitement. Les deux sont des pointeurs vers des caractères. C'est différent en ce qui concerne ce que vous déclarez. Mais le compilateur ajuste le type du paramètre pour être un pointeur vers un pointeur, même si vous avez dit que c'est un tableau. Ainsi, les éléments suivants sont tous les mêmes: void f (char * p [100]); void f (char * p []); void f (char ** p);
Johannes Schaub - litb

4
Dans C89 (que la plupart des gens utilisent), il n'y a également aucun moyen de tirer parti du fait que vous l'avez déclaré comme un tableau (donc sémantiquement, peu importe que vous y ayez déclaré un pointeur ou un tableau - les deux seront pris comme un aiguille). À partir de C99, vous pouvez bénéficier de le déclarer en tant que tableau. Ce qui suit dit: "p est toujours non nul et pointe vers une région d'au moins 100 octets": void f (char p [static 100]); Notez que par type, cependant, p est toujours un pointeur.
Johannes Schaub - litb

5
(en particulier, & p vous donnera un char **, mais pas un char ( ) [100] ce qui serait le cas si p * était un tableau). Je suis surpris que personne ne soit encore mentionné dans une réponse. Je considère qu'il est très important de comprendre.
Johannes Schaub - litb

Personnellement je préfère char**car cela me rappelle qu'il ne faut pas le traiter comme un vrai tableau, comme le faire sizeof[arr] / sizeof[*arr].
raymai97

9

Cela ne fait aucune différence, mais je l'utilise char *argv[]car cela montre qu'il s'agit d'un tableau de taille fixe de chaînes de longueur variable (qui sont généralement char *).



4

Cela ne fait pas vraiment de différence, mais ce dernier est plus lisible. Ce qui vous est donné est un tableau de pointeurs char, comme le dit la deuxième version. Il peut cependant être converti implicitement en un pointeur double char comme dans la première version.


2

vous devriez le déclarer comme char *argv[], en raison de toutes les nombreuses façons équivalentes de le déclarer, qui se rapproche le plus de sa signification intuitive: un tableau de chaînes.


1

char ** → pointeur vers le pointeur de caractère et char * argv [] signifie tableau de pointeurs de caractère. Comme nous pouvons utiliser un pointeur au lieu d'un tableau, les deux peuvent être utilisés.


0

Je ne vois aucun avantage particulier à utiliser l'une ou l'autre approche au lieu de l'autre - utilisez la convention qui correspond le mieux au reste de votre code.


-2

Si vous avez besoin d'un nombre variable ou dynamique de chaînes, char ** peut être plus facile à utiliser. Si votre nombre de chaînes est fixe, char * var [] sera préféré.


-2

Je sais que c'est obsolète, mais si vous apprenez juste le langage de programmation C et que vous ne faites rien de majeur avec lui, n'utilisez pas les options de ligne de commande.

Si vous n'utilisez pas d'arguments de ligne de commande, n'utilisez pas non plus. Déclarez simplement la fonction principale comme int main() si vous

  • Vous voulez que l'utilisateur de votre programme puisse faire glisser un fichier sur votre programme afin que vous puissiez modifier le résultat de votre programme avec lui ou
  • Vous souhaitez gérer les options de ligne de commande ( -help,/? ou toute autre chose qui va après program namedans le terminal ou invite de commande)

utilisez celui qui vous convient le mieux. Sinon, utilisez simplement int main() Après tout, si vous souhaitez ajouter des options de ligne de commande, vous pouvez facilement les modifier ultérieurement.


Cela ne répond pas à la question.
Lundin
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.