Comment un compilateur C sait-il que char ** x pointe sur un tableau?

En C, j’ai lu que char** x et char* x[] signifient exactement la même chose. Je comprends le 2ème exemple, mais je ne comprends pas le 1er exemple.

Pour moi, le premier exemple dit “pointeur sur un pointeur sur un caractère”, mais C verra ceci comme “pointer sur une liste de pointeurs sur des tableaux de caractères”?

Est-ce que quelqu’un serait capable de m’expliquer cela en termes simples, car j’ai eu une période inhabituellement difficile à saisir.

EDIT: J’avoue que j’ai formulé la première partie comme ce que je pensais que les gens voudraient entendre et non pas mon interprétation réelle. Moi aussi je le vois comme un pointeur vers un pointeur vers un caractère, mais ma compréhension est tombée lorsque j’ai lu la réponse principale à cette question: Différence entre char * argv [] et char ** argv pour le second argument de main ()

Ce qui montre qu’il est utilisé comme un pointeur sur un tableau de pointeurs de caractères lorsqu’il est utilisé comme vecteur d’argument …

Longue histoire courte – ça ne distingue pas ça. Le compilateur ne sait pas qu’il pointe vers un tableau. Tout ce qu’il voit est un pointeur. La façon dont vous avez montré (dans le cadre de la déclaration des parameters de la fonction – en passant à la fonction) char *[] et char ** dénote la même chose. Ce que c’est? Pointeur sur un personnage char* . Rien de plus que ça.

Un compilateur ne conserve aucune information supplémentaire permettant de distinguer si vous avez transmis un tableau char* ou un caractère char** car finalement, tableau char* décompose en pointeur sur le premier élément – ce qui correspond à caractère char** .

Essayez ceci, vous comprendriez: –

 char c = 'a'; char *pc = &c; char **ppc = &pc; f(ppc); 

….

 void f(char *ppc[]){ } 

Cela ne fera pas se plaindre le compilateur. Parce qu’il est finalement considéré par le compilateur comme char **ppc .

Pour prouver que les tableaux ne sont pas vus par le compilateur lorsqu’ils sont passés à la fonction – vous pouvez voir cette citation (qui dit en gros qu’il y a désintégration et que cela se résume à un pointeur sur le premier élément. Il voit donc tout un pointeur Cela ne donne en aucun cas l’idée que char** et char*[] sont identiques dans tous les cas à cause de la dégradation – ce n’est pas le cas)

§ 6.3.2.1 ¶3 c11 standard N1570

Sauf s’il s’agit de l’opérande de l’opérateur sizeof , de l’opérateur _Alignof ou de l’opérateur unaire & , ou d’un littéral de chaîne utilisé pour initialiser un tableau, une expression de type ‘tableau de type’ est convertie en une expression de type ‘ pointeur vers type ‘qui pointe sur l’élément initial de l’object tableau et n’est pas une lvalue . Si l’object tableau a une classe de stockage de registre, le comportement n’est pas défini.

Le assembly est fait pour effacer la mauvaise interprétation qui a amené quelqu’un à ne pas apprécier la réponse.

Comment un compilateur C sait-il que char ** x pointe sur un tableau?

Ce n’est pas!

Pour moi, le premier exemple dit “pointeur vers un pointeur vers un caractère”

Correct.

mais C verra ceci comme “pointer vers une liste de pointeurs vers des tableaux de caractères”?

Aussi correct.

Le truc, c’est que bien que le langage ne sache pas combien de temps la liste est longue, une liste avec une seule chose est toujours une liste. Donc, cela n’a pas vraiment d’importance. 1

Avec un pointeur T* ptr , *ptr vous donnera soit le seul object ptr par ptr (sur un), soit le premier object ptr par ptr (plus d’un).

Ce n’est que si vous commencez à incrémenter ce pointeur (par exemple, ptr++ , ou lors de l’access, comme ptr[152] ), que le nombre d’objects se trouvant réellement à l’autre extrémité est important. Encore une fois, peu importe la langue, cela revient au programmeur d’agir correctement.

1. Il existe des types pointeur vers tableau, il est donc possible d’introduire le système de types dans la discussion, ce qui est important. Mais nous ne discutons que des types pointeur vers object unique (malgré la réponse liée qui gêne le sujet en ce qui concerne le “passage” de tableaux par valeur).


Si cela vous aide, la déclaration liminaire de la première réponse à la question à laquelle vous avez lié:

Fondamentalement, char * argv [] signifie un tableau de pointeurs de caractères, alors que char ** argv signifie un pointeur sur un pointeur de caractères.

…est faux. char* argv[] est répété de manière transparente comme char** argv (merci beaucoup, C!), donc les deux signifient littéralement la même chose.

Vous devriez lire toutes les réponses sous une question, pas seulement une; Bmargulies est beaucoup plus précis.

Tout d’abord, char **x et char *x[] ne sont pas équivalents, sauf en tant que déclarations de paramètre de fonction.

Équivalent:

 int main(int argc, char **argv); int main(int argc, char *argv[]); // equivalent 

Pas équivalent:

 { char **pointer; char *array[3]; } 

Le pointeur AC T * peut pointer sur un seul object de type T ou sur l’un des éléments d’un “tableau de T “, mais il peut également pointer un élément au-delà de la fin d’un tel tableau.

Ces informations ne figurent pas dans les informations de type visibles par le programmeur au niveau de la langue du pointeur.

Dans certains cas, un compilateur peut déduire ces situations et fournir un diagnostic utile si des erreurs sont commises.

Un compilateur peut tracer le stream de données d’où provient la valeur d’une expression.

Par exemple, étant donné:

 char *array[3]; /* array of 3 "char *" pointers */ char **ptr = array; /* pointer to array[0] */ 

Ici, le compilateur peut parsingr que ptr été initialisé à partir d’un array . Ainsi, entre cette initialisation et tout autre point du programme, si aucune affectation à ptr n’est effectuée, on peut supposer qu’il pointe toujours vers le array[0] .

Il est donc possible que le compilateur produise un avertissement pour une expression douteuse telle que ptr + 5 , tout en restant silencieux pour ptr + 3 .

ISO C n’exige aucune parsing ou diagnostic de ce type; c’est une question de “qualité de mise en œuvre”.

Il y a des limites à ce qui peut être diagnostiqué statiquement. Les fonctions C peuvent recevoir des valeurs de pointeur sous forme de parameters “extravagants”, sur lesquels rien ne peut être raisonnablement connu au moment de la compilation.


En passant, le style de déclaration char* ptr est une mauvaise habitude; il devrait être appelé char *ptr . Notez que char* a, b déclare a tant que pointeur et b tant que char . La syntaxe est que char est une liste de “spécificateurs de déclaration”. Ces spécificateurs sont suivis d’une liste de “déclarateurs” séparés par des virgules. Ces déclarants sont *a et b , respectivement. Si vous écrivez char* ptr , vous placez une division d’espaces au milieu du déclarant, tout en l’agglomérant avec le spécificateur.

Cela revient un peu à prendre l’expression arithmétique x + x*y + y et à l’écrire sous la forme x+x * y+y , ce qui donne l’impression que la multiplication a été effectuée en dernier.

Évitez de tels espaces blancs trompeurs dans la programmation.

Pour simplifier l’explication, remplaçons char* par TypeName , qui peut être n’importe quoi.

TypeName* x et TypeName x[] sont exactement les mêmes quand ils sont utilisés comme parameters de fonction.

Certains utilisent le premier formulaire pour indiquer un pointeur sur un object unique et le second formulaire pour indiquer un pointeur sur le premier élément d’un tableau. Cependant, formez le sharepoint vue du compilateur, ils sont exactement les mêmes.

Disons que vous avez une variable:

 TypeName a[10]; 

Quand a est utilisé comme argument dans un appel de fonction, le désintégration du pointeur vers le premier élément

  foo(a); 

est le même que:

 foo(&a[0]); 

Dans la fonction, le paramètre peut être déclaré en tant que TypeName* x ou TypeName x[] .

Vous pouvez accéder aux éléments du tableau en utilisant la syntaxe de tableau, x[i] , quelle que soit la façon dont le paramètre est déclaré.

 void foo(TypeName* x) { x[0] = ; } 

et

 void foo(TypeName x[]) { x[0] = ; } 

sont identiques. Ils atsortingbuent une valeur au premier élément du tableau.

Dans le contexte d’une déclaration de paramètre de fonction, T a[N] et T a[] sont interprétés comme T *a – les trois déclarent a comme un pointeur sur T :

 void foo( T a[N] ) 

est le même que

 void foo( T a[] ) 

est le même que

 void foo( T *a ) 

Donc, remplacez T par char * , et vous obtenez

 void foo( char *arr[N] ) 

est le même que

 void foo( char *arr[] ) 

est le même que

 void foo( char **arr ) 

Ceci n’est vrai que pour une déclaration de paramètre de fonction – dans une déclaration régulière,

 T a[N]; 

n’est pas la même chose que

 T *a; 

Alors pourquoi est-ce le cas?

Sauf s’il s’agit de l’opérande sizeof ou des opérateurs unary & , ou d’un littéral utilisé pour initialiser un tableau de caractères dans une déclaration, une expression de type “tableau de N éléments” sera convertie (“decay”) en un expression de type “pointeur sur T “, et la valeur de l’expression sera l’adresse du premier élément du tableau.

Donc, si vous déclarez un tableau comme

 T a[N]; 

et le passer à une fonction en tant que

 foo( a ); 

l’ expression a dans l’appel de fonction est convertie du type “tableau N-élément de T ” en “pointeur sur T “, et ce que la fonction foo reçoit réellement est une valeur de pointeur, pas un tableau:

 void foo( T *a ) { ... } 

L’utilisation de a[] à la place de *a est un maintien du langage de programmation B, à partir duquel C a été dérivé. Franchement, beaucoup d’étranges tableaux de C peuvent être retrouvés dans B. Personnellement, je pense que cela crée plus de confusion que cela ne vaut la peine, mais je ne suis pas membre du comité qui écrit la norme.

Vous entendrez parfois dire que “un tableau est juste un pointeur”, ce qui est une interprétation erronée de la règle. Les tableaux et les pointeurs ne sont pas identiques, mais les expressions de tableau seront converties en expressions de pointeur dans la plupart des cas.