Les expressions de tableau sont spéciales en C.
6.3.2.1 Valeurs L, tableaux et désignateurs de fonctions
...
3 Sauf lorsqu'il s'agit de l'opérande de la fonction sizeof
l'opérateur, le _Alignof
ou l'opérateur unaire &
ou est un littéral de chaîne de caractères utilisé pour initialiser un tableau, une expression qui a type ''tableau de type'' est convertie en une expression de type ''pointeur de type'' qui pointe sur l'élément vers l'élément initial de l'objet tableau et qui n'est pas une lvalue. Si l'objet tableau a classe de stockage de registre, le comportement est indéfini.
C Projet en ligne 2011
Lorsque vous appelez
set_array(b);
el expression b
est converti du type "tableau à 4 éléments de int
"en "pointeur vers int
" ( int *
), et la valeur de l'expression est l'adresse de b[0]
. Par conséquent, ce qui set_array
reçoit est un pointeur valeur ( &b[0]
), et non un tableau.
6.7.6.3 Déclarateurs de fonctions (y compris les prototypes)
...
7 La déclaration d'un paramètre en tant que ''tableau de type'' doit être adaptée en ''pointeur qualifié de type ", où les qualificatifs de type (s'il y en a) sont ceux spécifiés dans l'expression [
y ]
de la dérivation du type de tableau. Si le mot-clé static
apparaît également dans le [
y ]
de la de la dérivation du type de tableau, alors, pour chaque appel à la fonction, la valeur de l'argument effectif correspondant doit permettre d'accéder au premier élément d'un tableau comportant au moins autant d'éléments que d'éléments. d'éléments que ceux spécifiés par l'expression de la taille.
ibid.
Fondamentalement, dans une définition de fonction, tout paramètre déclaré en tant que T a[N]
o T a[]
doit être interprété comme T *a
- Autrement dit, le paramètre est traité comme un pointeur, et non comme un tableau. Votre définition de fonction
void set_array(int array[4]){
array[0] = 22;
}
est traité comme s'il avait été écrit
void set_array(int *array){
array[0] = 22;
}
Les tableaux sont donc, en quelque sorte, passés par référence en C. Ce qui se passe réellement, c'est qu'un pointeur vers le premier élément du tableau est passé par valeur . Le site array
dans set_array
désigne un objet distinct en mémoire de b
Ainsi, toute modification apportée à array
lui-même n'ont aucun effet sur b
(C'est-à-dire que vous pourriez attribuer une nouvelle valeur à array
et le faire pointer vers un objet différent, mais cette affectation n'affecte pas b
du tout). Au lieu de cela, ce que vous faites est d'accéder à des éléments de b
par le biais de array
.
En image :
+---+
b: | | b[0] <---+
+---+ |
| | b[1] |
+---+ |
| | b[2] |
+---+ |
| | b[3] |
+---+ |
... |
+---+ |
array: | | ---------+
+---+
Une conséquence pratique de ceci est que sizeof array
donne la taille de la pointeur type int *
et non la taille du tableau, contrairement à sizeof b
. Cela signifie que vous ne pouvez pas compter le nombre d'éléments dans le tableau en utilisant la fonction sizeof array / sizeof array[0]
tour. Lorsque vous passez un tableau en tant qu'argument à une fonction, vous devez également passer la taille du tableau (c'est-à-dire le nombre d'éléments) comme un paramètre séparé. sauf si le tableau contient une valeur sentinelle bien définie (comme le terminateur 0 dans les chaînes de caractères).
Rappelez-vous que l'expression a[i]
es défini comme *(a + i)
- avec une adresse de départ a
, offset i
éléments (pas des octets !) à partir de cette adresse et déférer le résultat. Si a
est une expression de tableau, elle est d'abord convertie en expression de pointeur avant que ce calcul ne soit effectué. C'est pourquoi vous pouvez accéder aux éléments de b
par le biais de array
- array
stocke simplement le adresse du premier élément de b
.
En C, tous les arguments de fonction sont transmis par valeur - l'argument formel dans la définition de la fonction et l'argument réel dans l'appel de fonction font référence à des objets distincts en mémoire, et la valeur de l'argument réel est copiée dans l'argument formel. Les modifications apportées à l'argument formel ne sont pas reflétées dans l'argument réel.
Nous faux sémantique pass-by-reference en transmettant un pointeur au paramètre réel et écrire au pointeur déréférencé :
void foo( T *ptr )
{
*ptr = new_value(); // write a new value to the thing ptr points to
}
void bar( void )
{
T var;
foo( &var ); // have foo write a new value to var
}
Je veux dire, écrire à *ptr
est la même chose que d'écrire dans var
. Écrire à ptr
par contre, n'a aucun effet en dehors de foo
.
Dans un véritable système pass-by-reference (comme, par exemple, Fortran), l'argument formel dans la définition de la fonction et l'argument réel dans l'appel de la fonction désignent tous deux le même objet (au sens de "chose qui occupe de la mémoire et peut stocker des valeurs", et non au sens orienté objet "instance d'une classe"), de sorte que dans ces systèmes, toute modification de l'argument formel sont reflétée dans l'argumentation proprement dite (ce qui conduit à une question classique sur les Test de piratage "Avez-vous déjà changé la valeur de 4
? De manière non intentionnelle ? Dans un langage autre que Fortran ?")