web-dev-qa-db-fra.com

Pourquoi ne pouvons-nous pas passer des tableaux à fonctionner par valeur?

Apparemment, nous pouvons passer des instances de classes complexes à des fonctions, mais pourquoi ne pouvons-nous pas passer de tableaux à des fonctions?

38
Alcott

L'origine est historique. Le problème est que la règle "les tableaux se désintègrent en pointeurs lorsqu'ils sont transmis à une fonction" est simple.

Copier des tableaux serait un peu compliqué et pas très clair, car le comportement changerait pour différents paramètres et différentes déclarations de fonction.

Notez que vous pouvez toujours faire un passage indirect par valeur:

struct A { int arr[2]; };
void func(struct A);
52
Let_Me_Be

Voici une autre perspective: il n'y a pas un seul type "array" dans C. Au contraire, T[N] est un type différent pour chaque N. Donc, T[1], T[2], etc., sont tous différents types.

En C, il n'y a pas de surcharge de fonction, et la seule chose sensée que vous auriez pu autoriser serait une fonction qui prend (ou retourne) un type single de tableau:

void foo(int a[3]);  // hypothetical

Cela a sans doute été considéré comme beaucoup moins utile que la décision réelle de transformer tous les tableaux en un pointeur vers le premier élément et d’obliger l’utilisateur à communiquer la taille par un autre moyen. Après tout, ce qui précède pourrait être réécrit comme suit:

void foo(int * a)
{
  static const unsigned int N = 3;
  /* ... */
}

Il n'y a donc pas de perte de pouvoir expressif, mais un gain énorme en termes de généralité.

Notez que cela n’est pas différent en C++, mais la génération de code basée sur des modèles vous permet d’écrire une fonction basée sur un modèle foo(T (&a)[N]), où N est déduit pour vous - mais cela signifie simplement que vous pouvez créer une famille entière de distinct différentes fonctions, une pour chaque valeur de N.

Dans les cas extrêmes, imaginez que vous ayez besoin de deux fonctions print6(const char[6]) et print12(const char[12]) pour dire print6("Hello") et print12("Hello World") si vous ne voulez pas décomposer les tableaux en pointeurs, sinon vous devrez ajouter une conversion explicite, print_p((const char*)"Hello World").

25
Kerrek SB

Répondant à une question très ancienne, comme Question is market et que C++ ne fait que l'ajouter à des fins de complétion, nous pouvons utiliser std :: array et passer des tableaux à des fonctions par valeur ou par référence, ce qui protège des accès indéfinis:

ci-dessous est un exemple:

#include <iostream>
#include <array>

//pass array by reference
template<size_t N>
void fill_array(std::array<int, N>& arr){
    for(int idx = 0; idx < arr.size(); ++idx)
        arr[idx] = idx*idx;
}

//pass array by value
template<size_t N>
void print_array(std::array<int, N> arr){
    for(int idx = 0; idx < arr.size(); ++idx)
        std::cout << arr[idx] << std::endl;
}

int main()
{
    std::array<int, 5> arr;
    fill_array(arr);
    print_array(arr);
    //use different size
    std::array<int, 10> arr2;
    fill_array(arr2);
    print_array(arr2);
}
4
Ayub

La raison pour laquelle vous ne pouvez pas passer un tableau par valeur est qu’il n’existe aucun moyen spécifique de suivre la taille d’un tableau, de sorte que la logique d’appel de la fonction puisse connaître la quantité de mémoire à allouer et le contenu à copier. Vous pouvez passer une instance de classe car les classes ont des constructeurs. Les tableaux ne le font pas.

3
David Schwartz

L'équivalent serait de faire d'abord une copie du tableau, puis de la transmettre à la fonction (ce qui peut être très inefficace pour les grands tableaux). 

En dehors de cela, je dirais que c’est pour des raisons historiques, c’est-à-dire qu’il est impossible de passer des tableaux en valeur en C.

Mon hypothèse est que le raisonnement à l'origine de la non introduction de tableaux de passage par valeur en C++ était que les objets étaient considérés comme étant de taille moyenne par rapport aux tableaux.

Comme indiqué par delnan, lorsque vous utilisez std::vector, vous pouvez réellement transmettre des objets de type tableau à des fonctions par valeur.

0
Andre Holzner

Vous êtes en passant par valeur: la valeur du pointeur sur le tableau. N'oubliez pas que l'utilisation de la notation entre crochets en C est simplement un raccourci pour déférencer un pointeur. ptr [2] signifie * (ptr + 2). 

En supprimant les crochets, vous obtenez un pointeur sur le tableau, qui peut être transmis par valeur à une fonction:

int x[2] = {1, 2};
int result;
result = DoSomething(x);

Voir le liste des types dans la spécification ANSI C. Les tableaux ne sont pas des types primitifs, mais sont construits à partir d'une combinaison de pointeurs et d'opérateurs. (Cela ne me permettra pas de mettre un autre lien, mais la construction est décrite dans la section "Dérivation du type Array".)

0
KonradG

Cela a été fait ainsi afin de préserver la compatibilité syntaxique et sémantique avec le langage B, dans lequel les tableaux étaient implémentés en tant que pointeurs physiques.

Une réponse directe à cette question est donnée dans Dennis/Ritchie "Le développement du langage C" , voir la section "Critique". Ça dit

Par exemple, les crochets vides dans la déclaration de fonction

int f(a) int a[]; { ... }

sont un fossile vivant, un vestige de la façon dont le Nouveau-Brunswick a déclaré un pointeur; a est, dans ce cas particulier uniquement, interprété en C comme un pointeur. La notation a survécu en partie pour des raisons de compatibilité, en partie grâce à la rationalisation selon laquelle elle permettrait aux programmeurs de communiquer à leurs lecteurs l’intention de transmettre à f un pointeur généré à partir d’un tableau plutôt qu’une référence à un seul entier. Malheureusement, cela sert autant à confondre l'apprenant qu'à alerter le lecteur.

Cela devrait être pris dans le contexte de la partie précédente de l'article, en particulier "Embryonic C", qui explique comment l'introduction de types struct en C a entraîné le rejet de l'approche de type B et BCPL pour la mise en œuvre de tableaux (en tant que pointeurs ordinaires) . C a basculé vers une implémentation de tableau sans pointeur, conservant cette sémantique héritée de style B dans les listes de paramètres de fonction uniquement.

Ainsi, la variante actuelle du comportement des paramètres de tableau est le résultat d’un compromis: d’une part, nous avons dû avoir des tableaux copiables dans structs, d’autre part, nous voulions préserver la compatibilité sémantique avec les fonctions écrites en B, où les tableaux sont toujours passé "par pointeur".

0
AnT

Été:

  1. Passer le Adresse du premier élément du tableau&a = a = &(a[0])
  2. Nouveau pointeur (nouveau pointeur, nouvelle adresse, 4 octets, en mémoire)
  3. Pointe sur le même emplacement mémoire, dans type différent.

Exemple 1:

void by_value(bool* arr) // pointer_value passed by value
{
    arr[1] = true;
    arr = NULL; // temporary pointer that points to original array
}

int main()
{
    bool a[3] = {};
    cout << a[1] << endl; // 0
    by_value(a);
    cout << a[1] << endl; // 1 !!! 
}

Adresses:

[main] 
     a = 0046FB18 // **Original**
     &a = 0046FB18 // **Original**
[func]
     arr = 0046FB18 // **Original**
     &arr = 0046FA44 // TempPTR
[func]
     arr = NULL
     &arr = 0046FA44 // TempPTR

Exemple 2:

void by_value(bool* arr) 
{
    cout << &arr << arr; // &arr != arr
}

int main()
{
    bool a[3] = {};
    cout << &a << a; // &a == a == &a[0]
    by_value(arr);
}

Adresses

Prints: 
[main] 0046FB18 = 0046FB18
[func] 0046FA44 != 0046FB18

Notez s'il vous plaît:

  1. & (required-lvalue): lvalue -to-> rvalue
  2. Array Decay: nouveau pointeur (temporaire) pointe vers (par valeur) l'adresse du tableau

Lire la suite:

Rvalue

Array Decay

0
Almog