Type de tableau – Règles d’affectation / d’utilisation en tant que paramètre de fonction

quand j’ai besoin de passer un tableau à une fonction, il semble que toutes les déclarations suivantes de la fonction fonctionneront

void f(int arr[]) void f(int arr[4]) // is this one correct? 

pour ça:

 int a[]={1,2,3,4}; f(a); 

Mais quand j’assigne un tableau à un autre tableau, il échoue

 int a[]={1,2,3,4}; int b[4] = a; // error: array must be initialized with a brace-enclosed initializer 

Alors pourquoi un tableau passé en tant qu’argument d’une fonction est correct, mais utilisé sur les rhs d’une simple affectation est faux?

Pour comprendre la différence, nous devons comprendre deux contextes différents.

  • Dans les contextes de valeur , le nom d’un tableau de type T équivaut à un pointeur sur le type T et équivaut à un pointeur sur le premier élément du tableau.
  • Dans les contextes d’ object , le nom d’un tableau de type T ne se réduit pas à un pointeur.

Qu’est-ce que le contexte d’object?

Dans a = b; , a est dans un contexte d’object. Lorsque vous prenez l’adresse d’une variable, elle est utilisée dans un contexte d’object. Enfin, lorsque vous utilisez l’opérateur sizeof sur une variable, il est utilisé dans un contexte d’object. Dans tous les autres cas, une variable est utilisée dans un contexte de valeur.

Maintenant que nous avons cette connaissance, quand nous le faisons:

 void f(int arr[4]); 

C’est exactement équivalent à

 void f(int *arr); 

Comme vous l’avez découvert, nous pouvons omettre la taille (4 ci-dessus) de la déclaration de fonction. Cela signifie que vous ne pouvez pas connaître la taille du “tableau” passé à f() . Plus tard, quand tu le feras:

 int a[]={1,2,3,4}; f(a); 

Dans l’appel de fonction, le nom a trouve dans le contexte de la valeur, ce qui le réduit à un pointeur sur int . C’est bien, parce que f attend un pointeur sur un int , la définition de la fonction et l’utilisation correspondent. Ce qui est passé à f() est le pointeur sur le premier élément de a ( &a[0] ).

Dans le cas de

 int a[]={1,2,3,4}; int b[4] = a; 

Le nom b est utilisé dans un contexte d’object et ne se réduit pas à un pointeur. (Incidemment, a ici est dans un contexte de valeur et se réduit à un pointeur.)

Maintenant, int b[4]; atsortingbue une valeur de stockage de 4 int s et lui donne le nom b . a également été assigné à un stockage similaire. Ainsi, en réalité, l’affectation ci-dessus signifie: “Je souhaite que l’emplacement de stockage soit identique à l’emplacement précédent”. Cela n’a pas de sens.

Si vous voulez copier le contenu de a dans b , vous pouvez faire:

 #include  int b[4]; memcpy(b, a, sizeof b); 

Ou, si vous vouliez un pointeur b pointant vers a :

 int *b = a; 

Ici, a est dans le contexte de la valeur et se réduit à un pointeur sur int , afin que nous puissions affecter a à un int * .

Enfin, lors de l’initialisation d’un tableau , vous pouvez lui atsortingbuer des valeurs explicites:

 int a[] = {1, 2, 3, 4}; 

Ici, a a 4 éléments, initialisés à 1, 2, 3 et 4. Vous pouvez aussi faire:

 int a[4] = {1, 2, 3, 4}; 

S’il y a moins d’éléments dans la liste que le nombre d’éléments dans le tableau, le rest des valeurs est pris à 0:

 int a[4] = {1, 2}; 

définit a[2] et a[3] sur 0.

 void f(int arr[]); void f(int arr[4]); 

La syntaxe est trompeuse. Ils sont tous les deux pareils:

 void f(int *arr); 

c’est-à-dire que vous passez un pointeur au début du tableau. Vous ne copiez pas le tableau.

C ne prend pas en charge l’affectation de tableaux. Dans le cas d’un appel de fonction, le tableau se décompose en un pointeur. C prend en charge l’affectation de pointeurs. C’est ce qui est demandé ici à peu près tous les jours – quel livre C lisez-vous qui n’explique pas cela?

Essayez Memcpy.

 int a[]={1,2,3,4}; int b[4]; memcpy(b, a, sizeof(b)); 

Merci de l’avoir signalé, Steve, cela fait longtemps que je n’ai pas utilisé C.

Pour obtenir votre intuition à ce sujet, vous devez comprendre ce qui se passe au niveau de la machine.

La sémantique d’initialisation (= {1,2,3,4}) signifie “placez-la sur votre image binary exactement de cette façon” pour que cela puisse être compilé.

L’affectation d’un tableau serait différente: le compilateur devrait le traduire en une boucle, ce qui aurait pour effet de parcourir des éléments. Le compilateur C (ou C ++, d’ailleurs) ne fait jamais une telle chose. Il attend à juste titre que vous le fassiez vous-même. Pourquoi? Parce que vous pouvez. Donc, cela devrait être un sous-programme, écrit en C (memcpy). C’est une question de simplicité et de proximité avec votre arme, c’est-à-dire C et C ++.

Notez que le type d’ a in int a[4] est int [4] .

Mais TypeOf ( &a ) == int (*)[4] ! = int [4] .

Notez également que le type de la valeur de a est int * , ce qui est différent de tout ce qui précède!

Voici un exemple de programme que vous pouvez essayer:

 int main() { // All of these are different, incompatible types! printf("%d\n", sizeof (int[4])); // 16 // These two may be the same size, but are *not* interchangeable! printf("%d\n", sizeof (int (*)[4])); // 4 printf("%d\n", sizeof (int *)); // 4 } 

Je veux clarifier. Il y a quelques indices trompeurs dans les réponses … Toutes les fonctions suivantes peuvent prendre des tableaux de nombres entiers:

 void f(int arr[]) void f(int arr[4]) void f(int *arr) 

Mais les arguments formels ne sont pas les mêmes. Donc, le compilateur peut les gérer différemment. Dans le sens de la gestion de la mémoire interne, tous les arguments mènent à des pointeurs.

 void f(int arr[]) 

… f () prend un tableau de toute taille.

 void f(int arr[4]) 

… L’argument formel indique une taille de tableau.

 void f(int *arr) 

… Vous pouvez également passer un pointeur entier. f () ne sait rien de la taille.