34 votes

Pourquoi les tableaux ne peuvent-ils pas être passés comme arguments de fonction ?

Pourquoi ne pouvez-vous pas passer des tableaux comme arguments de fonction ?

J'ai lu ce livre sur le C++ qui dit "vous ne pouvez pas passer des tableaux comme arguments de fonction", mais il n'explique jamais pourquoi. De plus, lorsque j'ai fait des recherches en ligne, j'ai trouvé des commentaires du type "pourquoi feriez-vous cela de toute façon ?". Ce n'est pas que je le ferais, je veux juste savoir pourquoi on ne peut pas le faire.

45voto

Lightness Races in Orbit Points 122793

Pourquoi les tableaux ne peuvent-ils pas être passés comme arguments de fonction ?

Ils le peuvent :

void foo(const int (&myArray)[5]) {
   // `myArray` is the original array of five integers
}

En termes techniques, le type de l'argument à foo est "référence à un tableau de 5 const int s" ; avec les références, on peut passer le réel l'objet autour ( clause de non-responsabilité : la terminologie varie selon le niveau d'abstraction) .

Ce que vous ne pouvez pas faire, c'est passer par valeur car, pour des raisons historiques, nous n'allons pas copie les tableaux. Au lieu de cela, tenter de passer un tableau par valeur dans une fonction (ou, pour passer un copie d'un tableau) conduit son nom à se décomposer en un pointeur. ( certaines ressources se trompent ! )


Les noms de tableaux se décomposent en pointeurs pour le passage par valeur.

Cela signifie :

void foo(int* ptr);

int ar[10]; // an array
foo(ar);    // automatically passing ptr to first element of ar (i.e. &ar[0])

Il y a aussi le très trompeur "sucre syntaxique" qui regarde comme vous pouvez passer un tableau de longueur arbitraire par valeur :

void foo(int ptr[]);

int ar[10]; // an array
foo(ar);

Mais, en fait, vous ne faites que passer un pointeur (vers le premier élément du fichier ar ). foo est le même qu'en haut !

Tant que nous y sommes, la fonction suivante également n'a pas vraiment la signature qu'il semble avoir. Regardez ce qui se passe lorsque nous essayons d'appeler cette fonction sans la définir :

void foo(int ar[5]);
int main() {
   int ar[5];
   foo(ar);
}

// error: undefined reference to `func(int*)'

Alors foo prend int* en fait, no int[5] !

( Démonstration en direct. )


Mais vous pouvez le contourner !

Vous pouvez contourner ce problème en enveloppant le tableau dans un struct o class car l'opérateur de copie par défaut sera copier le tableau :

struct Array_by_val
{
  int my_array[10];
};

void func (Array_by_val x) {}

int main() {
   Array_by_val x;
   func(x);
}

Ce comportement est quelque peu déroutant.


Ou, mieux, une approche générique de type "pass-by-reference".

En C++, avec un peu de magie des modèles, nous pouvons rendre une fonction à la fois réutilisable et capable de recevoir un tableau :

template <typename T, size_t N>
void foo(const T (&myArray)[N]) {
   // `myArray` is the original array of N Ts
}

Mais on ne peut toujours pas en passer une par valeur. Quelque chose à retenir.


L'avenir...

Et comme C++11 se profile à l'horizon et que la prise en charge de C++0x est en bonne voie dans les chaînes d'outils grand public, vous pouvez utiliser l'adorable outil std::array hérité de Boost ! Je laisse au lecteur le soin de faire des recherches à ce sujet.

14voto

Dietrich Epp Points 72865

Je vois donc des réponses expliquant : "Pourquoi le compilateur ne me permet-il pas de faire ça ?". Plutôt que "Qu'est-ce qui a poussé la norme à spécifier ce comportement ?". La réponse se trouve dans l'histoire du C. Ceci est extrait de "The Development of the C Language" ( fuente ) par Dennis Ritchie.

Dans les langages proto-C, la mémoire était divisée en "cellules" contenant chacune un mot. Ces cellules pouvaient être déréférencées à l'aide de l'éventuelle fonction unaire * opérateur -- oui, il s'agissait essentiellement de langages sans typage comme certains langages jouets d'aujourd'hui comme Brainf_ck. Le sucre syntaxique permettait de prétendre qu'un pointeur était un tableau :

a[5]; // equivalent to *(a + 5)

Ensuite, l'allocation automatique a été ajoutée :

auto a[10]; // allocate 10 cells, assign pointer to a
            // note that we are still typeless
a += 1;     // remember that a is a pointer

A un moment donné, le auto le comportement du spécificateur de stockage est devenu par défaut -- vous pouvez également vous demander quel est l'intérêt de l'option auto Le mot-clé l'était de toute façon, le voici. Ces changements incrémentiels ont laissé les pointeurs et les tableaux se comporter de manière quelque peu bizarre. Peut-être les types se comporteraient-ils de manière plus similaire si le langage était conçu à partir d'une vue d'ensemble. En l'état actuel des choses, il ne s'agit que d'une erreur de plus dans le C/C++.

5voto

Keith Thompson Points 85120

Les tableaux sont en quelque sorte des types de seconde classe, ce que le C++ a hérité du C.

Citation de 6.3.2.1p3 dans la norme C99 :

Sauf lorsqu'il s'agit de l'opérande de la fonction taille de ou l'opérateur unaire & unaire, ou est un littéral de chaîne de caractères utilisé pour initialiser un tableau, une qui a le type "tableau de type "est converti en une de type "pointeur vers type "qui pointe vers l'élément initial initial de l'objet tableau et n'est pas une lvalue. Si l'objet tableau a une classe de stockage de registre, le comportement est indéfini.

Le même paragraphe dans le Norme C11 est essentiellement le même, avec l'ajout de la nouvelle _Alignof opérateur. (Les deux liens renvoient à des projets qui sont très proches des normes officielles. ( UPDATE : C'était en fait une erreur dans le projet N1570, corrigée dans la norme C11 publiée. _Alignof ne peut pas être appliqué à une expression, seulement à un nom de type entre parenthèses, donc C11 a seulement les mêmes 3 exceptions que C99 et C90. (Mais je m'égare.)))

Je n'ai pas la citation C++ correspondante sous la main, mais je crois que c'est assez similaire.

Donc si arr est un objet de type tableau, et vous appelez une fonction func(arr) entonces func recevra un pointeur vers le premier élément de arr .

Jusqu'à présent, il s'agit plus ou moins de "ça marche comme ça parce que c'est défini comme ça", mais il y a des raisons historiques et techniques à cela.

Autoriser les paramètres de tableau ne permettrait pas une grande flexibilité (sans autres modifications du langage), puisque, par exemple, char[5] y char[6] sont des types distincts. Même le passage de tableaux par référence n'y contribue pas (à moins qu'il n'y ait une fonctionnalité du C++ qui me manque, ce qui est toujours possible). Passer des pointeurs vous donne une énorme flexibilité (peut-être trop !). Le pointeur peut pointer vers le premier élément d'un tableau de n'importe quelle taille - mais vous devez créer votre propre mécanisme pour indiquer à la fonction la taille du tableau.

Concevoir un langage de sorte que des tableaux de longueurs différentes soient quelque peu compatibles tout en restant distincts est en fait assez délicat. En Ada, par exemple, les équivalents de char[5] y char[6] sont les mêmes type mais différent sous-types . Dans les langages plus dynamiques, la longueur fait partie de la valeur d'un objet tableau, et non de son type. Le C continue à se débattre avec des pointeurs et des longueurs explicites, ou des pointeurs et des terminateurs. Le C++ a hérité de tout ce bagage du C. Il a surtout fait l'impasse sur la question des tableaux et a introduit les vecteurs, de sorte qu'il n'était pas nécessaire de faire des tableaux des types de première classe.

TL;DR : C'est du C++, vous devriez utiliser des vecteurs de toute façon ! (Enfin, parfois.)

2voto

ccozad Points 759

Les tableaux ne sont pas transmis par valeur car ils sont essentiellement des blocs continus de mémoire. Si vous aviez un tableau que vous vouliez transmettre par valeur, vous pouviez le déclarer dans une structure et y accéder par l'intermédiaire de cette structure.

Cela a des conséquences sur les performances car cela signifie que vous allez verrouiller plus d'espace sur la pile. Passer un pointeur est plus rapide car l'enveloppe de données à copier sur la pile est bien moindre.

1voto

nhutto Points 53

Je pense que la raison pour laquelle le C++ a fait cela était, lors de sa création, qu'il aurait pu prendre trop de ressources pour envoyer le tableau entier plutôt que l'adresse en mémoire. Il ne s'agit que de mes réflexions sur le sujet et d'une supposition.

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