web-dev-qa-db-fra.com

Qu'est-ce qu'un tableau en décomposition?

Qu'est-ce que la décomposition d'un tableau? Existe-t-il une relation avec les pointeurs de tableau?

318
Vamsi

On dit que les tableaux "se décomposent" en indicateurs. Un tableau C++ déclaré comme int numbers [5] ne peut pas être re-pointé, c'est-à-dire que vous ne pouvez pas dire numbers = 0x5a5aff23. Plus important encore, le terme décroissance signifie la perte de type et de dimension; numbers se désintègre en int* en perdant les informations de dimension (nombre 5) et le type n'est plus int [5]. Regardez ici pour les cas où la dégradation ne se produit pas .

Si vous passez un tableau par valeur, vous copiez réellement un pointeur - un pointeur sur le premier élément du tableau est copié dans le paramètre (dont le type doit également être un pointeur du type de l'élément tableau). Cela fonctionne en raison de la nature en décomposition de la matrice; une fois décomposé, sizeof ne donne plus la taille de la matrice complète, car elle devient essentiellement un pointeur. C'est pourquoi il est préférable (entre autres) de passer par référence ou par pointeur.

Trois façons de passer dans un tableau1:

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 des informations sizeof correctes, tandis que le premier ne le fera pas, car l'argument de tableau est tombé en désuétude pour être attribué au paramètre.

1 La constante U doit être connue au moment de la compilation.

234
phoebus

Les tableaux sont fondamentalement les mêmes que les pointeurs en C/C++, mais pas tout à fait. Une fois que vous convertissez un tableau:

const int a[] = { 2, 3, 5, 7, 11 };

dans un pointeur (qui fonctionne sans transtypage et peut donc se produire de manière inattendue dans certains cas):

const int* p = a;

vous perdez la capacité de l'opérateur sizeof à compter les éléments du tableau:

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

Cette capacité perdue est appelée "décomposition".

Pour plus de détails, consultez cet article sur la détérioration de tableau .

85
system PAUSE

Voici ce que dit la norme (C99 6.3.2.1/3 - Autres opérandes - Lvalues, tableaux et indicateurs de fonction):

Sauf s'il s'agit de l'opérande de l'opérateur sizeof ou de l'opérateur unaire &, ou s'il s'agit d'un le littéral de chaîne utilisé pour initialiser un tableau, une expression de type ‘'tableau de type’ ’est converti en une expression de type ‘pointeur sur type’ qui pointe vers l’élément initial de l'objet tableau et n'est pas une lvalue.

Cela signifie que pratiquement chaque fois que le nom du tableau est utilisé dans une expression, il est automatiquement converti en un pointeur sur le premier élément du tableau.

Notez que les noms de fonction agissent de la même manière, mais que les pointeurs de fonction sont beaucoup moins utilisés et d'une manière beaucoup plus spécialisée qu'ils ne causent pas autant de confusion que la conversion automatique des noms de tableaux en pointeurs.

La norme C++ (conversion de tableau en pointeur 4.2) assouplit l'exigence de conversion en (emphase le mien):

Une lvalue ou rvalue de type «tableau de N T» ou «tableau de bornes inconnues de T» can peut être convertie en une rvalue de type "pointeur vers T."

Donc, la conversion n'a pas doit se produire de la même manière qu'en C (cela permet aux fonctions de surcharge ou aux modèles de correspondre au type de tableau).

C’est aussi pourquoi, en C, vous devriez éviter d’utiliser des paramètres de tableau dans les prototypes/définitions de fonctions (à mon avis - je ne suis pas sûr qu’il existe un accord général). Ils causent de la confusion et sont de toute façon une fiction - utilisez des paramètres de pointeur et la confusion ne disparaîtra peut-être pas complètement, mais au moins, la déclaration de paramètre ne ment pas.

43
Michael Burr

"Decay" fait référence à la conversion implicite d'une expression d'un type de tableau en type de pointeur. Dans la plupart des contextes, lorsque le compilateur voit une expression de tableau, il convertit le type de l'expression de "tableau de N-éléments de T" en "pointeur en T" et définit la valeur de l'expression sur l'adresse du premier élément du tableau. . Les exceptions à cette règle s'appliquent lorsqu'un tableau est un opérande des opérateurs sizeof ou &, ou lorsque le tableau est un littéral de chaîne utilisé en tant qu'initialiseur dans une déclaration. 

Supposons le code suivant:

char a[80];
strcpy(a, "This is a test");

L'expression a est de type "tableau de caractères à 80 éléments" et l'expression "Ceci est un test" est de type "tableau de caractères à 16 éléments" (en C; en C++, les littéraux de chaîne sont des tableaux de caractères const). Toutefois, dans l'appel à strcpy(), aucune des deux expressions n'étant un opérande de sizeof ou &, leurs types sont donc implicitement convertis en "pointeur sur caractère" et leurs valeurs sont définies sur l'adresse du premier élément de chaque élément. Ce que strcpy() reçoit ne sont pas des tableaux, mais des pointeurs, comme le montre son prototype:

char *strcpy(char *dest, const char *src);

Ce n'est pas la même chose qu'un pointeur de tableau. Par exemple:

char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;

ptr_to_first_element et ptr_to_array ont la même valeur valeur ; l'adresse de base de a. 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]

N'oubliez pas que l'expression a[i] est interprétée en tant que *(a+i) (qui ne fonctionne que si le type de tableau est converti en un type de pointeur), de sorte que a[i] et ptr_to_first_element[i] fonctionnent de la même manière. L'expression (*ptr_to_array)[i] est interprétée comme *(*a+i). Les expressions *ptr_to_array[i] et ptr_to_array[i] peuvent conduire à des avertissements ou des erreurs du compilateur selon le contexte; ils feront certainement la mauvaise chose si vous vous attendez à ce qu'ils évaluent a[i].

sizeof a == sizeof *ptr_to_array == 80

De nouveau, lorsqu'un tableau est un opérande de sizeof, il n'est pas converti en un type de 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 sur char. 

25
John Bode

Les tableaux, en C, n'ont aucune valeur.

Partout où la valeur d'un objet est attendue mais que l'objet est un tableau, l'adresse de son premier élément est utilisée à la place, avec le type pointer to (type of array elements).

Dans une fonction, tous les paramètres sont passés par valeur (les tableaux ne font pas exception). Lorsque vous passez un tableau dans une fonction, celle-ci "se décompose en un pointeur" (sic); lorsque vous comparez un tableau à autre chose, il se "décompose en un pointeur" (sic); ...

void foo(int arr[]);

La fonction foo attend la valeur d'un tableau. Mais, en C, les tableaux n'ont aucune valeur! Donc, foo obtient à la place l'adresse du premier élément du tableau.

int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }

Dans la comparaison ci-dessus, arr n'a pas de valeur et devient donc un pointeur. Cela devient un pointeur sur int. Ce pointeur peut être comparé à la variable ip.

Dans la syntaxe d'indexation de tableau que vous avez l'habitude de voir, encore une fois, l'arr est "décomposé en un pointeur"

arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */

Les seules fois où un tableau ne se désintègre pas en un pointeur, ce sont les opérandes de l'opérateur sizeof, l'opérateur & (l'opérateur 'address of') ou un littéral de chaîne utilisé pour initialiser un tableau de caractères.

12
pmg

C'est quand le tableau pourrit et est pointé 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 l'enfer transmettrait tout le tableau pour vous), les gens disent que ce pauvre tableau se décompose en pointeur.

6

Array decaying signifie que, lorsqu'un tableau est passé en paramètre à une fonction, il est traité de manière identique à un "pointeur".

void do_something(int *array) {
  // We don't know how big array is here, because it's decayed to a pointer.
  printf("%i\n", sizeof(array));  // always prints 4 on a 32-bit machine
}

int main (int argc, char **argv) {
    int a[10];
    int b[20];
    int *c;
    printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
    printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
    printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
    do_something(a);
    do_something(b);
    do_something(c);
}

Il existe deux complications ou exceptions à ce qui précède.

Tout d’abord, lorsqu’il s’agit de tableaux multidimensionnels en C et C++, seule la première dimension est perdue. En effet, les tableaux sont disposés de manière contiguë en mémoire. Le compilateur doit donc connaître la totalité des dimensions sauf la première dimension pour pouvoir calculer les décalages dans ce bloc de mémoire.

void do_something(int array[][10])
{
    // We don't know how big the first dimension is.
}

int main(int argc, char *argv[]) {
    int a[5][10];
    int b[20][10];
    do_something(a);
    do_something(b);
    return 0;
}

Deuxièmement, en C++, vous pouvez utiliser des modèles pour déduire la taille des tableaux. Microsoft l'utilise pour les versions C++ des fonctions Secure CRT telles que strcpy_s , et vous pouvez utiliser une astuce similaire pour obtenir de manière fiable le nombre d'éléments d'un tableau .

2
Josh Kelley

tl; dr: lorsque vous utilisez un tableau que vous avez défini, vous utilisez en fait un pointeur sur son premier élément.

Ainsi:

  • Lorsque vous écrivez arr[idx], vous ne faites que dire *(arr + idx).
  • les fonctions ne prennent jamais vraiment les tableaux en tant que paramètres, uniquement les pointeurs, même lorsque vous spécifiez un paramètre de tableau.

Tri des exceptions à cette règle:

  • Vous pouvez passer des tableaux de longueur fixe à des fonctions dans une struct.
  • sizeof() donne la taille occupée par le tableau, pas la taille d'un pointeur.
0
einpoklum