Qu'est-ce que la décroissance du tableau par rapport au pointeur ? Y a-t-il un rapport avec les pointeurs de tableau ?
Comment le premier passe-t-il par valeur ?
Qu'est-ce que la décroissance du tableau par rapport au pointeur ? Y a-t-il un rapport avec les pointeurs de tableau ?
On dit que les tableaux se "décomposent" en pointeurs. Un tableau C++ déclaré comme int numbers [5]
ne peut pas être recollée, c'est-à-dire que vous ne pouvez pas dire numbers = 0x5a5aff23
. Plus important encore, le terme "décadence" signifie la perte de type et de dimension ; numbers
se dégrader en int*
en perdant l'information sur la dimension (compte 5) et le type n'est pas int [5]
plus. Regardez ici pour les cas où la dégradation ne se produit pas .
Si vous passez un tableau par valeur, ce que vous faites réellement est de copier un pointeur - un pointeur sur le premier élément du tableau est copié sur le paramètre (dont le type devrait également être un pointeur sur le type de l'élément du tableau). Cela fonctionne grâce à la nature décroissante du tableau ; une fois décroissant, sizeof
ne donne plus la taille complète du tableau, car elle devient essentiellement un pointeur. C'est pourquoi il est préférable (entre autres raisons) de passer par référence ou par pointeur.
Trois façons de passer dans un tableau 1 :
void by_value(const T* array) // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])
Les deux derniers donneront une bonne sizeof
info, alors que la première ne le fera pas puisque l'argument du tableau s'est décomposé pour être assigné au paramètre.
1 La constante U doit être connue au moment de la compilation.
By_value est le passage d'un pointeur vers le premier élément du tableau ; dans le contexte des paramètres de fonction, T a[]
est identique à T *a
. by_pointer fait passer la même chose, sauf que la valeur du pointeur est maintenant qualifiée const
. Si vous voulez passer un pointeur au tableau (par opposition à un pointeur vers le premier élément du tableau), la syntaxe est la suivante T (*array)[U]
.
"avec un pointeur explicite vers ce tableau" - c'est incorrect. Si a
est un tableau de char
entonces a
est de type char[N]
et se décomposera en char*
mais &a
est de type char(*)[N]
et sera no la décomposition.
Les tableaux sont fondamentalement les mêmes que les pointeurs en C/C++, mais pas tout à fait. Une fois que vous avez converti un tableau :
const int a[] = { 2, 3, 5, 7, 11 };
en un pointeur (ce qui fonctionne sans casting, et peut donc se produire de manière inattendue dans certains cas) :
const int* p = a;
vous perdez la capacité de la sizeof
pour compter les éléments du tableau :
assert( sizeof(p) != sizeof(a) ); // sizes are not equal
Cette perte de capacité est appelée "déchéance".
Pour plus de détails, consultez ce document article sur la dégradation du tableau .
Les tableaux sont no sont fondamentalement les mêmes que les pointeurs ; ce sont des animaux complètement différents. Dans la plupart des contextes, un tableau peut être traité comme suit comme si il s'agissait d'un pointeur, et un pointeur peut être traité comme si c'était un tableau, mais c'est le plus proche qu'ils obtiennent.
John, veuillez pardonner l'imprécision de mon langage. J'essayais de trouver la réponse sans m'enliser dans une longue histoire, et "en gros... mais pas tout à fait" est la meilleure explication que j'ai pu obtenir à l'université. Je suis sûr que toute personne intéressée peut se faire une idée plus précise en lisant votre commentaire.
Voici ce que dit la norme (C99 6.3.2.1/3 - Autres opérandes - Valeurs L, tableaux et désignateurs de fonctions) :
Sauf lorsqu'il s'agit de l'opérande de l'opérateur sizeof ou de l'opérateur unaire &, ou encore d'un littéral de chaîne de caractères utilisé pour initialiser un tableau, une expression de type ''tableau de type'' est convertie en une expression de type ''pointeur de type'' qui pointe vers l'élément initial de l'objet tableau et qui n'est pas un linaire. l'objet tableau et n'est pas une lvalue.
Cela signifie qu'à peu près chaque fois que le nom du tableau est utilisé dans une expression, il est automatiquement converti en un pointeur vers le premier élément du tableau.
Notez que les noms de fonctions agissent de la même manière, mais les pointeurs de fonctions sont beaucoup moins utilisés et de manière beaucoup plus spécialisée que la conversion automatique des noms de tableaux en pointeurs, ce qui n'entraîne pas autant de confusion.
La norme C++ (4.2 Array-to-pointer conversion) assouplit l'exigence de conversion à (c'est moi qui souligne) :
Une valeur lval ou rval de type "array of N T" ou "array of unknown bound of T". puede être converti en une valeur r de type "pointeur vers T".
Donc la conversion n'est pas ont comme cela se passe presque toujours en C (cela permet aux fonctions de surcharger ou aux modèles de correspondre au type de tableau).
C'est également pour cette raison qu'en C, vous devriez éviter d'utiliser des paramètres de tableau dans les prototypes/définitions de fonction (à mon avis - je ne suis pas sûr qu'il y ait un accord général). Ils sont source de confusion et sont de toute façon une fiction - utilisez des paramètres pointeurs et la confusion ne disparaîtra peut-être pas complètement, mais au moins la déclaration du paramètre ne ment pas.
Quel est un exemple de ligne de code où une "expression de type 'tableau de type'" est "une chaîne littérale utilisée pour initialiser un tableau" ?
"Décroissance" se réfère à la conversion implicite d'une expression à partir d'un tableau de type pour un type de pointeur. Dans la plupart des contextes, lorsque le compilateur voit un tableau de l'expression, il convertit le type de l'expression "N-élément de tableau de T" à "pointeur de T" et définit la valeur de l'expression à l'adresse du premier élément du tableau. Les exceptions à cette règle lorsqu'un tableau est un opérande de l' sizeof
ou &
opérateurs, ou le tableau est un littéral de chaîne utilisé comme un initialiseur dans une déclaration.
Supposons le code suivant:
char a[80];
strcpy(a, "This is a test");
L'expression a
est de type "80-élément de tableau de char" et l'expression "Ceci est un test" est de type "16-élément de tableau de char" (en C, en C++, les littéraux de chaîne sont des tableaux de const char). Cependant, dans l'appel d' strcpy()
, ni l'expression est un opérande de sizeof
ou &
, de sorte que leurs types sont implicitement converti en pointeur de char", et de leurs valeurs à l'adresse du premier élément de chaque. Ce strcpy()
reçoit ne sont pas des tableaux, mais des pointeurs, comme on le voit dans son prototype:
char *strcpy(char *dest, const char *src);
Ce n'est pas la même chose qu'un pointeur sur le tableau. Par exemple:
char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;
Les deux ptr_to_first_element
et ptr_to_array
ont la même valeur; l'adresse de base d'une. Cependant, ils sont de types différents et sont traités différemment, comme indiqué ci-dessous:
a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]
Rappelez-vous que l'expression a[i]
est interprété comme *(a+i)
(qui ne fonctionne que si le type du tableau est converti en un type pointeur), de sorte que les deux a[i]
et ptr_to_first_element[i]
fonctionner de la même. L'expression (*ptr_to_array)[i]
est interprété comme *(*a+i)
. Les expressions *ptr_to_array[i]
et ptr_to_array[i]
peut conduire à des avertissements du compilateur ou des erreurs en fonction du contexte; ils vont certainement faire quelque chose de mal si vous vous attendez à eux de les évaluer à l' a[i]
.
sizeof a == sizeof *ptr_to_array == 80
Encore une fois, lorsqu'un tableau est un opérande de sizeof
, il n'est pas converti en un type pointeur.
sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
is on your platform
ptr_to_first_element
est un simple pointeur vers char.
C'est quand le tableau pourrit et qu'on le pointe du doigt ;-)
En fait, c'est juste que si vous voulez passer un tableau quelque part, mais que le pointeur est passé à la place (parce que qui diable passerait le tableau entier pour vous), les gens disent que le pauvre tableau s'est dégradé en pointeur.
Bien dit. Quel serait un tableau agréable qui ne se dégrade pas en pointeur ou qui est empêché de se dégrader ? Pouvez-vous citer un exemple en C ? Merci.
Je ne suis pas sûr de ce que vous entendez par "travailler". Il n'est pas permis d'accéder au-delà du tableau, bien que cela fonctionne comme prévu si vous vous attendez à ce qui doit réellement se produire. Ce comportement (bien que, encore une fois, officiellement non défini) est préservé.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.
80 votes
Peu connu : L'opérateur unaire plus peut être utilisé comme un "opérateur de désintégration" : Étant donné
int a[10]; int b(void);
entonces+a
est un pointeur int et+b
est un pointeur de fonction. Utile si vous voulez le passer à un modèle acceptant une référence.3 votes
@litb - les parenthèses feraient la même chose (par exemple, (a) devrait être une expression qui évalue un pointeur), non ?
25 votes
std::decay
de C++14 serait une façon moins obscure de décomposer un tableau sur des + unaires.23 votes
@JohannesSchaub-litb puisque cette question est étiquetée à la fois C et C++, j'aimerais préciser que même si
+a
y+b
sont légaux en C++, ils sont illégaux en C (C11 6.5.3.3/1 "L'opérande de la fonction unaire+
o-
l'opérateur doit être de type arithmétique")0 votes
@matt vous avez raison. Mais j'ai déjà mentionné que cette bonté ne fonctionne qu'en C++. Je vous remercie tout de même de l'avoir noté pour le cas où quelqu'un d'autre manquerait ma note également.
5 votes
@lege Right. Mais je suppose que ce n'est pas aussi peu connu que l'astuce du + unaire. La raison pour laquelle je l'ai mentionné n'est pas simplement parce qu'il se désintègre, mais parce que c'est un truc amusant avec lequel on peut jouer ;)
0 votes
@JohannesSchaub-litb : Ça m'a complètement échappé, même là. existe a opérateur unaire plus :-(
0 votes
@JohannesSchaub-litb : cela pose la question de savoir si l'utilisation d'une caractéristique peu connue de la langue est une bonne pratique. Quel effet cela a-t-il sur la lisibilité du code, tant pour les autres développeurs actuels de l'équipe que pour les futurs développeurs ?
0 votes
Un terme inventé. Jusqu'à l'ajout de
std::decay
avec C++11 le mot "decay" apparaît exactement zéro dans les normes C ou C++ tout au long de leur cycle de vie. Durée de vie . C'est toujours le cas pour C. Il y a quelques années, j'ai posé une question sur SO pour savoir qui avait inventé ce terme. Personne ne le savait. Le plus loin que l'on ait pu remonter était une réponse à un post uunet datant de 1987. Je me suis alors demandé ce que les gens utilisaient pour embrouiller les nouveaux ingénieurs C avant cela.0 votes
@WhozCraig C'est parce que la norme utilise une terminologie plus formelle. Voir §6.7.6.3 7.