99 votes

Passer un tableau comme argument à une fonction en C

J'ai écrit une fonction contenant un tableau comme argument, et je l'appelle en passant la valeur du tableau comme suit.

void arraytest(int a[])
{
    // changed the array a
    a[0]=a[0]+a[1];
    a[1]=a[0]-a[1];
    a[0]=a[0]-a[1];
}

void main()
{
    int arr[]={1,2};
    printf("%d \t %d",arr[0],arr[1]);
    arraytest(arr);
    printf("\n After calling fun arr contains: %d\t %d",arr[0],arr[1]);
}

Ce que j'ai trouvé, c'est que même si j'appelle arraytest() en passant des valeurs, la copie originale de int arr[] est modifié.

Pouvez-vous expliquer pourquoi ?

157voto

Bo Persson Points 42821

Lorsque vous passez un tableau comme paramètre, cette

void arraytest(int a[])

signifie exactement la même chose que

void arraytest(int *a)

alors vous sont modifier les valeurs dans main.

Pour des raisons historiques, les tableaux ne sont pas des citoyens de première classe et ne peuvent pas être transmis par valeur.

16voto

Gabriel Staples Points 1804

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 :

  1. signifie exactement la même chose que

     void arraytest(int a[0])
  2. ce qui signifie exactement la même chose que

     void arraytest(int a[1])
  3. ce qui signifie exactement la même chose que

     void arraytest(int a[2])
  4. ce qui signifie exactement la même chose que

     void arraytest(int a[1000])
  5. 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 :

  1. Mon expérimentation du code en ligne : https://onlinegdb.com/B1RsrBDFD

Voir aussi :

  1. 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++ ?

9voto

Si vous voulez passer un tableau unidimensionnel comme argument dans une fonction vous devez déclarer un paramètre formel de l'une des trois manières suivantes, et les trois méthodes de déclaration produisent des résultats similaires, car chaque paramètre formel est différent. indique au compilateur qu'un pointeur entier va être reçu .

int func(int arr[], ...){
    .
    .
    .
}

int func(int arr[SIZE], ...){
    .
    .
    .
}

int func(int* arr, ...){
    .
    .
    .
}

Vous modifiez donc les valeurs d'origine.

Merci ! !!

8voto

fyr Points 7756

Vous ne passez pas le tableau en tant que copie. C'est seulement un pointeur qui pointe vers l'adresse où se trouve le premier élément du tableau en mémoire.

7voto

obounaim Points 1213

Vous passez l'adresse du premier élément du tableau.

Prograide.com

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.

Powered by:

X