1. Utilisation standard d'un tableau en C avec une dégradation naturelle du type de tableau à ptr.
@Bo Persson indique correctement dans sa grande réponse ici :
Lorsque vous passez un tableau comme paramètre, cette
void arraytest(int a[])
signifie exactement la même chose que
void arraytest(int *a)
Cependant, permettez-moi d'ajouter que les deux formes ci-dessus aussi :
-
signifie exactement la même chose que
void arraytest(int a[0])
-
ce qui signifie exactement la même chose que
void arraytest(int a[1])
-
ce qui signifie exactement la même chose que
void arraytest(int a[2])
-
ce qui signifie exactement la même chose que
void arraytest(int a[1000])
-
etc.
*Dans chacun des exemples de tableaux ci-dessus, et comme le montre l'exemple d'appels dans le code juste en dessous, le type de paramètre d'entrée se transforme en un paramètre de type `int ** et peut être appelé sans avertissement et sans erreur, même avec les options de construction suivantes
-Wall -Wextra -Werror` activé (voir mon repo ici pour plus de détails sur ces 3 options de construction), comme ceci :
int array1[2];
int * array2 = array1;
// works fine because `array1` automatically decays from an array type
// to `int *`
arraytest(array1);
// works fine because `array2` is already an `int *`
arraytest(array2);
En fait, la valeur "taille" ( [0]
, [1]
, [2]
, [1000]
) à l'intérieur du paramètre de tableau est apparemment juste pour des raisons esthétiques/d'auto-documentation, et peut être n'importe quel nombre entier positif ( size_t
type je pense) que vous voulez !
En pratique, cependant, vous devriez l'utiliser pour spécifier la taille minimale du tableau que vous attendez que la fonction reçoive, afin que lors de l'écriture du code, il soit facile pour vous de le suivre et de le vérifier. Le site MISRA-C-2012 standard ( acheter/télécharger le PDF de 236 pages de la version 2012 de la norme pour £15.00 ici ) va jusqu'à affirmer (c'est nous qui soulignons) :
Règle 17.5 L'argument de fonction correspondant à un paramètre déclaré de type tableau doit comporter un nombre approprié d'éléments.
...
Si un paramètre est déclaré sous la forme d'un tableau dont la taille est spécifiée, l'argument correspondant dans chaque appel de fonction doit pointer vers un objet comportant au moins autant d'éléments que le tableau.
...
L'utilisation d'un déclarateur de tableau pour un paramètre de fonction spécifie l'interface de la fonction plus clairement que l'utilisation d'un pointeur. Le nombre minimum d'éléments attendus par la fonction est explicitement indiqué, ce qui n'est pas possible avec un pointeur.
En d'autres termes, ils recommandent d'utiliser le format de taille explicite, même si la norme C ne l'impose pas techniquement cela permet au moins de clarifier pour vous, en tant que développeur, et pour les autres personnes utilisant le code, la taille du tableau que la fonction attend de vous.
2. Forcer la sécurité de type sur les tableaux en C
(Non recommandé, mais possible. Voir mon bref argument contre cette pratique à la fin. Aussi, pour ma version multi-dimensionnelle [ex : tableau 2D] de ceci, voir ici .)
Comme @Winger Sendon le fait remarquer dans un commentaire sous ma réponse, nous pouvons forcer C à traiter un tableau type pour être différent en fonction du tableau taille !
Tout d'abord, vous devez reconnaître que dans mon exemple ci-dessus, l'utilisation de la fonction int array1[2];
comme ça : arraytest(array1);
causes array1
pour se décomposer automatiquement en un int *
. CEPENDANT, si vous prenez le l'adresse de array1
à la place et appeler arraytest(&array1)
vous obtenez un comportement complètement différent ! Maintenant, il ne se désintègre pas en un int *
! Au lieu de cela, le type de &array1
est *`int ()[2]` ce qui signifie "pointeur vers un tableau de taille 2 de int" ou "pointeur vers un tableau de taille 2 de type int". ou dit aussi "pointeur vers un tableau de 2 ints". . Ainsi, vous pouvez FORCER C à vérifier la sécurité de type sur un tableau, comme ceci :**
void arraytest(int (*a)[2])
{
// my function here
}
Cette syntaxe est difficile à lire, mais similaire à celle d'un pointeur de fonction . L'outil en ligne, cdecl nous dit que int (*a)[2]
signifie : "déclarer a comme pointeur vers le tableau 2 de int" (pointeur vers un tableau de 2 int
s). Ne confondez PAS cette version avec la version sans parenthèses : int * a[2]
ce qui signifie : "déclarer a comme tableau 2 de pointeur vers int" (AKA : tableau de 2 pointeurs à int
Alias : tableau de 2 int*
s).
Maintenant, cette fonction EXIGE que vous l'appeliez avec l'opérateur d'adresse ( &
) comme ceci, en utilisant comme paramètre d'entrée un pointeur vers un tableau de la bonne taille :
int array1[2];
// ok, since the type of `array1` is `int (*)[2]` (ptr to array of
// 2 ints)
arraytest(&array1); // you must use the & operator here to prevent
// `array1` from otherwise automatically decaying
// into `int *`, which is the WRONG input type here!
Cependant, cela produira un avertissement :
int array1[2];
// WARNING! Wrong type since the type of `array1` decays to `int *`:
// main.c:32:15: warning: passing argument 1 of ‘arraytest’ from
// incompatible pointer type [-Wincompatible-pointer-types]
// main.c:22:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
arraytest(array1); // (missing & operator)
Vous pouvez testez ce code ici .
Pour forcer le compilateur C à transformer cet avertissement en erreur, de sorte que vous DEVEZ toujours appeler arraytest(&array1);
en utilisant seulement un tableau d'entrée de la taille correcte et type ( int array1[2];
dans ce cas), ajoutez -Werror
à vos options de construction. Si vous exécutez le code de test ci-dessus sur onlinegdb.com, faites-le en cliquant sur l'icône d'engrenage en haut à droite et cliquez sur "Extra Compiler Flags" pour saisir cette option. Maintenant, cet avertissement :
main.c:34:15: warning: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Wincompatible-pointer-types]
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
se traduira par cette erreur de construction :
main.c: In function ‘main’:
main.c:34:15: error: passing argument 1 of ‘arraytest’ from incompatible pointer type [-Werror=incompatible-pointer-types]
arraytest(array1); // warning!
^~~~~~
main.c:24:6: note: expected ‘int (*)[2]’ but argument is of type ‘int *’
void arraytest(int (*a)[2])
^~~~~~~~~
cc1: all warnings being treated as errors
Notez que vous pouvez également créer des pointeurs "sûrs" vers des tableaux d'une taille donnée, comme ceci :
int array[2];
// "type safe" ptr to array of size 2 of int:
int (*array_p)[2] = &array;
...mais je n'ai PAS nécessairement Je ne recommande pas cela (l'utilisation de ces tableaux "sûrs en termes de types" en C), car cela me rappelle beaucoup les singeries du C++ utilisées pour imposer la sécurité des types partout, au prix d'une complexité syntaxique exceptionnellement élevée du langage, d'une verbosité et d'une difficulté à architecturer le code, et que je déteste et sur lesquelles j'ai déjà fulminé à maintes reprises (ex : voir "Mes réflexions sur le C++" ici ).
Pour d'autres tests et expérimentations, voir aussi le lien juste en dessous.
Références
Voir les liens ci-dessus. Aussi :
- Mon expérimentation du code en ligne : https://onlinegdb.com/B1RsrBDFD
Voir aussi :
- Ma réponse sur les tableaux multidimensionnels (ex : tableaux 2D) qui développe ce qui précède, et utilise l'approche de "sécurité de type" pour les tableaux multidimensionnels quand cela a du sens : Comment passer un tableau multidimensionnel à une fonction en C et C++ ?