web-dev-qa-db-fra.com

Déclarer un tableau d'int

Y a-t-il une différence entre ces deux déclarations?

int x[10];

vs.

int* x = new int[10];

Je suppose que la première déclaration (comme la dernière) est une déclaration de pointeur et que les deux variables peuvent être traitées de la même manière. Est-ce que cela signifie qu'ils sont intrinsèquement les mêmes?

62
Meysam
#include<iostream>    

int y[10];


void doSomething()
{
    int x[10];
    int *z  = new int[10];
    //Do something interesting

    delete []z;
}

int main()
{
    doSomething();

}

Un séjour sans faille

int x[10]; 

- Crée un tableau d'entiers de taille 10 sur la pile.
- Vous n’avez pas besoin de supprimer explicitement cette mémoire car elle disparaît à mesure que la pile se déroule.
- Son étendue est limitée à la fonction doSomething()

int y[10];

- Crée un tableau d'entiers de taille 10 sur le segment BSS/Data.
- Vous n'êtes pas obligé de supprimer explicitement cette mémoire.
- Puisqu'il est déclaré global il est accessible globalement.

int *z = new int[10];

- Alloue un tableau dynamique d'entiers de taille 10 sur le tas et renvoie l'adresse de cette mémoire à z.
- Vous devez supprimer explicitement cette mémoire dynamique après l'avoir utilisée. en utilisant:

delete[] z;
74
Alok Save

La seule chose semblable entre

int x[10];

et

int* x = new int[10];

est-ce que l’un ou l’autre peut être utilisé dans certains contextes où un int* est attendu:

int* b = x;   // Either form of x will work

void foo(int* p) {}

foo(x);      // Either form will work

Cependant, ils ne peuvent pas être utilisés dans tous les contextes où un int* Est attendu. Plus précisément,

delete [] x;  // UB for the first case, necessary for the second case.

Certaines des différences fondamentales ont été expliquées dans les autres réponses. Les autres différences fondamentales sont:

Différence 1

sizeof(x) == sizeof(int)*10   // First case

sizeof(x) == sizeof(int*)     // Second case.

Différence 2

Le type de &x Est int (*)[10] dans le premier cas

Le type de &x Est int** Dans le second cas

Différence

Fonction donnée

void foo(int (&arr)[10]) { }

vous pouvez l'appeler en utilisant le premier x et non le second x.

foo(x);     // OK for first case, not OK for second case.
8
R Sahu

Selon le standard, il convient de distinguer trois types de déclarations de tableau:

int x[10];

void method() {
     int y[10];
     int *z = new int[10];
     delete z;
}

La première déclaration, int x[10], Utilise une durée de stockage statique , définie par cppreference comme: "Le stockage pour l'objet est alloué au démarrage du programme et désalloué à la fin du programme. Une seule instance de l'objet existe. Tous les objets déclarés dans la portée de l'espace de noms (y compris l'espace de noms global) ont cette durée de stockage, plus ceux déclarés avec static ou extern. "

Le second, int y[10], Utilise la durée de stockage automatique , définie par cppreference comme: "L'objet est alloué au début du bloc de code englobant et libéré à la fin. Tous les objets locaux ont cette durée de stockage, à l'exception de ceux déclarés static, extern ou thread_local. "

Le troisième, int *z = new int[10], Est habituellement appelé allocation de mémoire dynamique et consiste en une séquence en deux étapes:

  • Tout d'abord, l'opérateur new est appelé. Il alloue de la mémoire de manière dynamique en utilisant soit les méthodes d'allocation par défaut de la bibliothèque standard, soit une implémentation définie par l'utilisateur (car new peut être remplacé lors de l'exécution). La mémoire allouée sera suffisante pour contenir les N éléments alloués, plus toute mémoire supplémentaire requise pour conserver les métadonnées pour l'allocation donnée (afin qu'elle puisse ensuite être libérée avec succès).
  • Deuxièmement, si la première étape réussit, nous procédons ensuite à l’initialisation ou à la construction de chaque objet du tableau.

Comme déjà mentionné par d'autres commentaires, ces types de déclaration ont leurs différences subtiles, mais les plus communes sont:

  1. Sur la plupart des systèmes d'exploitation modernes:

    • automatique Le stockage est généralement alloué sur la pile, qui est (en règle générale) un espace mémoire pré-alloué spécifique à un thread à l'aide d'un LIFO
    • static Le stockage utilise l'espace mémoire préalloué réservé dans l'exécutable ( plus précisément les segments .BSS et .DATA, selon que la variable est zéro) initialisé ou non )
    • dynamique la mémoire est allouée à l'aide de la mémoire heap et est soumise à la pitié du système de gestion du système RAM et autres mécanismes comme la pagination.
  2. La mémoire allouée dynamiquement devrait être explicitement delete- édité par le programmeur, alors que les variables de stockage statiques et automatiques sont prises en charge par "l'environnement"

  3. Les variables de stockage statiques et automatiques sont limitées à une étendue spécifique, alors que la mémoire allouée dynamiquement n'a pas de limite, ce qui signifie qu'une variable déclarée dans un module peut être transmise à tout autre module fonctionnant dans le même espace adresse.

  4. Lors de l'allocation d'un tableau en utilisant new[], La taille peut être 0

  5. (Comme déjà souligné par @R Sahu) Les types de &x Et &z Sont différents:

    • &x Est int (*)[10]
    • &z Est int **
6
Daniel Trugman

Le premier est un tableau de int de taille 10. Dire que sa création sur pile est fausse. Parce que la norme ne garantit pas cela. Sa mise en œuvre définie. Sa durée de stockage pourrait être statique ou automatique selon que x soit global variable ou local variable.

Dans le second cas, vous créez un pointeur de type int*. Pas nécessairement créé sur le tas, la norme ne dit pas cela. La mémoire allouée couvre 10 * sizeof(int) octets. Pour cela, vous devez désallouer vous-même la mémoire en écrivant:

delete [] x; 

Dans ce cas, la mémoire du pointeur x est allouée dynamiquement et libérée dynamiquement, de sorte que ces objets sont dits avoir durée de stockage dynamique.

6
Nawaz

Les déclarations sont complètement différentes.

Le premier cas,

int x[10];

déclare x comme un tableau de 10 entier, alors que le second cas,

int* x = new int[10];

déclare x en tant que pointeur sur int - une variable de valeur égale à l'adresse d'un int et initialise ce pointeur sur le résultat d'une nouvelle expression (new int [10]) Qui alloue dynamiquement un tableau de dix entiers.

Nonobstant les différences, les deux peuvent être utilisés de manière similaire;

  • la syntaxe du tableau (par exemple, x[i], où i est une valeur intégrale comprise entre 0 et 9 inclus) peut être utilisée pour définir ou récupérer les valeurs des tableaux respectifs. dans la syntaxe ci-dessus;
  • une arithmétique de pointeur peut être utilisée pour obtenir l'adresse d'un élément du tableau (par exemple, x + i équivaut à &x[i] pour i entre 0 et 10 Inclus. [Oui, il est possible d’obtenir une adresse "un après la fin"];
  • Le déréférencement du pointeur et l'accès au tableau sont équivalents. ie *(x+i) et x[i] sont équivalents, pour i entre 0 et 9 [déréférencer un pointeur "un après la fin" donne comportement indéfini].

Cependant, il existe également certaines différences clés, par exemple:

Résultats de l'opérateur sizeof. sizeof(x) donne des valeurs différentes dans les deux cas.

  1. Dans le premier cas, sizeof(x) == sizeof(int)*10. sizeof(int) donne une balise définie par l'implémentation, mais sizeof(x)/sizeof(*x) donnera toujours le nombre d'éléments dans le tableau (c'est-à-dire un std::size_t avec la valeur 10) .
  2. Dans le second, sizeof(x) == sizeof(int *) - qui est une valeur définie par l'implémentation. La valeur de sizeof(x)/sizeof(*x) ne donne pratiquement pas une valeur de 10. Ce qui signifie que cette technique ne peut pas être utilisée pour obtenir le nombre d'éléments.

Durée de vie.

  1. Dans le premier cas, la durée de vie de x dépend de la portée de la déclaration. Si la déclaration se produit au niveau du fichier (c'est-à-dire dans une unité de compilation, en dehors de tout bloc de fonction), alors x a une durée de stockage statique (existe donc tant que le programme est en cours d'exécution). Si la déclaration se produit dans un bloc, x - et tous ses éléments - cessent d'exister à la fin du bloc. Par exemple

    {
        int x[10];
    
    }    //  x and all its elements cease to exist here
    
  2. Dans le second cas, seul le pointeur x a une durée de vie qui dépend de la portée. La mémoire allouée dynamiquement (le résultat de new x[10]) N'est jamais désallouée. Cela signifie que la durée de vie de x et celle du tableau (alloué de manière dynamique) qu'elle référence sont découplées, ce qui nous amène à une troisième différence .....

Résultat de l'affectation Un tableau ne peut pas être réaffecté, un pointeur peut (à moins que const soit qualifié).

Considérons un contexte de

 // x as previously defined in one or the other form

 int y[10];
 int z;

 x = y;
 x = &z;

Dans le premier cas, les deux assignations donneront lieu à un diagnostic du compilateur - les assignations ne sont pas valides. Dans le second cas, les assignations sont valides et font que x pointe sur l'adresse de (le premier élément de) y et sur l'adresse de z respectivement. Sauf si la valeur de x est stockée dans un autre pointeur avant la réaffectation, la mémoire allouée par la nouvelle expression (new int [10]) Est perdue - elle n'est plus accessible par le programme, mais elle n'est pas libérée non plus. .

3
Peter

Si vous souhaitez dimensionner un tableau de manière dynamique, par exemple:

void doSomething(int size)
{
    int x[size];               // does not compile
    int *z  = new int[size];
    //Do something interesting ...
    doMore(z, size);
}

alors x ne sera pas compilé en C++, vous devez donc utiliser z. La bonne nouvelle est que vous pouvez maintenant utiliser z, dans la plupart des cas, comme s'il était alloué statiquement, par exemple:

void doMore(int anArray[], int size)
{
    // ...
}

prend z en argument, le pointeur étant un tableau.

0
William H. Hooper

Ils sont les mêmes tant que les deux x pointent vers la première adresse mémoire du tableau de 10 entiers, bien que très différent en ce que

int x[10] 

déclare la mémoire en mémoire vive statique, et le mot-clé 'new' les crée dynamiquement avec le tas, ce qui revient à utiliser malloc dans c pour créer dynamiquement un tableau.

Non seulement cela, mais (je ne pense pas avoir testé la théorie), il y a une chance que:

int* x = new int[10];

pourrait échouer et, selon le compilateur, pourrait renvoyer une erreur ou un pointeur null. Si le compilateur c ++ adhère aux normes ANSI/ISO, il prend en charge une forme "no-throw" de new qui renvoie un null si l'allocation échoue, plutôt que de lever une exception.

L'autre différence est que le "nouvel" opérateur peut être surchargé.

Ce dont je ne suis pas sûr, cependant, est si l’un ou l’autre (en c ++) crée un tableau terminé par zéro. Je sais que dans c, dans le compilateur que j'utilise au moins, vous devez vous assurer de toujours ajouter un\0 à toutes les chaînes ou tableaux si vous vous attendez à pouvoir les parcourir sans surcharger les limites.

Juste ma valeur de 0,02 $. :)

0
Steven S

Premier cas: x est créé sur un segment de pile/données en fonction de la variable locale non statique ou de la variable statique/globale. Et l'adresse de x n'est pas modifiable.

Deuxième cas: 'x' est un pointeur pointant sur un tableau créé généralement sur heap (free store). Vous pouvez aussi changer x en pointant vers autre chose. De plus, vous devez vous occuper de le désallouer en utilisant delete[] x;

0
iammilind