Question

D'accord, je pense que nous sommes tous d'accord pour dire que ce qui se passe avec le code suivant est défini, en fonction de ce qui est passé,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

Le pointeur peut être toutes sortes de choses différentes, et ainsi effectuer une delete[] inconditionnelle est indéfinie. Cependant, supposons que nous passons en effet un pointeur de tableau,

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

Ma question est, dans ce cas où le pointeur est un tableau, qui est-ce qui sait? Je veux dire, du point de langage / compilateur de vue, il n'a aucune idée si oui ou non arr est un pointeur de tableau par rapport à un pointeur vers un int. Heck, il ne sait même pas si arr a été créé de façon dynamique. Pourtant, si je fais ce qui suit à la place,

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

Le système d'exploitation est assez intelligent pour ne supprimer un int et ne pas aller sur un certain type de « tuerie » en supprimant le reste de la mémoire au-delà de ce point (qui contraste avec strlen et une chaîne non terminée \0-- il va continuer jusqu'à ce qu'elle touche 0).

dont le travail est-il de se rappeler ces choses? Est-ce que le système d'exploitation garder un certain type d'enregistrement en arrière-plan? (Je veux dire, je me rends compte que j'ai commencé ce post en disant que ce qui se passe est indéfini, mais le fait est, le scénario « tuerie » ne se produit pas, donc par conséquent dans le monde pratique quelqu'un remémoration.)

Était-ce utile?

La solution

Le compilateur ne sait pas qu'il est un tableau, il est confiance au programmeur. Suppression d'un pointeur vers un seul int avec delete [] entraînerait un comportement non défini. Votre deuxième exemple main() est dangereux, même si elle ne tombe pas en panne immédiatement.

Le compilateur ne garder une trace de combien d'objets doivent être supprimés en quelque sorte. Il peut faire de plus-allocation suffisante pour stocker la taille du tableau. Pour plus de détails, consultez le C ++ super FAQ .

Autres conseils

Une question que les réponses données jusqu'ici semblent ne pas répondre: si les bibliothèques d'exécution (pas le système d'exploitation, vraiment) peuvent garder une trace du nombre de choses dans le tableau, alors pourquoi avons-nous besoin de la syntaxe de delete[] à tout? Pourquoi ne peut pas une seule forme de delete être utilisé pour gérer toutes les suppressions?

La réponse à cela remonte à C racines de ++ comme langage C-compatible (qu'elle ne lutte plus vraiment être.) La philosophie de Stroustrup était que le programmeur ne devrait pas avoir à payer pour toutes les fonctionnalités qu'ils n'utilisent pas. Si elles ne sont pas en utilisant des tableaux, ils ne devraient pas avoir à supporter le coût des tableaux d'objets pour chaque morceau de mémoire alloué.

C'est, si votre code ne fonctionne tout simplement

Foo* foo = new Foo;

alors l'espace mémoire qui est allouée pour foo ne devrait pas inclure une charge supplémentaire qui serait nécessaire pour soutenir des réseaux de Foo.

Étant donné que les allocations de tableau sont mis en place pour transporter les informations supplémentaires de la taille du tableau, vous devez ensuite dire aux bibliothèques d'exécution pour rechercher cette information lorsque vous supprimez les objets. Voilà pourquoi nous devons utiliser

delete[] bar;

au lieu de simplement

delete bar;

si la barre est un pointeur sur un tableau.

Pour la plupart d'entre nous (moi y compris), que fussiness de quelques octets supplémentaires de mémoire semble étrange ces jours-ci. Mais il y a encore des situations où sauver quelques octets (de ce qui pourrait être un très grand nombre de blocs de mémoire) peut être important.

Oui, le système d'exploitation conserve certaines choses dans le « fond ». Par exemple, si vous exécutez

int* num = new int[5];

le système d'exploitation peut allouer 4 octets supplémentaires, stocker la taille de la répartition dans les 4 premiers octets de la mémoire allouée et renvoyer un pointeur de décalage (par exemple, il alloue des espaces de mémoire 1000-1024 mais le pointeur est retourné de points à 1004, avec 1000-1003 emplacements de stockage de la taille de l'allocation). Puis, quand suppression est appelé, il peut regarder à 4 octets avant le pointeur qui lui est passé pour trouver la taille de l'allocation.

Je suis sûr qu'il ya d'autres moyens de suivre la taille d'une allocation, mais c'est une option.

Ceci est très similaire à ce question et il a beaucoup de détails votre recherchez.

Mais il suffit de dire, ce n'est pas le travail du système d'exploitation pour suivre tout cela. Il est en fait les bibliothèques d'exécution ou le gestionnaire de mémoire sous-jacente qui permettra de suivre la taille du tableau. Cela se fait habituellement par l'allocation de mémoire supplémentaire à l'avant et à stocker la taille de la matrice dans cette position (la plupart utilisent un noeud de tête).

Ceci est visible sur certaines implémentations en exécutant le code suivant

int* pArray = new int[5];
int size = *(pArray-1);

delete ou delete[] probablement à la fois libérer la mémoire allouée (mémoire pointée), mais la grande différence est que delete sur un tableau ne sera pas appeler la destructor de chaque élément du tableau.

Quoi qu'il en soit, le mélange new/new[] et delete/delete[] est probablement UB.

Il ne sait pas qu'il est un tableau, c'est pourquoi vous devez fournir delete[] au lieu de l'ancien delete régulier.

J'avais une question semblable à cela. En C, vous allouez mémoire avec malloc () (ou une autre fonction similaire), et supprimez-free (). Il n'y a qu'un seul malloc (), qui alloue simplement un certain nombre d'octets. Il n'y a qu'un seul libre (), qui prend simplement un pointeur comme il est paramètre.

Alors, pourquoi est-ce que dans C, vous pouvez simplement la main sur le pointeur pour libérer, mais en C ++, vous devez lui dire que ce soit un tableau ou d'une seule variable?

La réponse, je l'ai appris, a à voir avec la classe Destructeurs.

Si vous allouez une instance d'une classe MyClass ...

classes = new MyClass[3];

et supprimez-le avec suppression, vous ne pouvez obtenir le destructor pour la première instance de MyClass appelé. Si vous utilisez supprimer [], vous pouvez être assuré que le destructor sera appelé à toutes les instances du tableau.

est la différence importante. Si vous travaillez simplement avec des types standards (par exemple int) vous ne verrez pas vraiment cette question. De plus, vous devez vous rappeler que le comportement à l'aide de supprimer le nouveau [] et supprimer [] sur le nouveau n'est pas défini -. Il peut ne pas fonctionner de la même manière sur tous les compilateur / système

Il est à l'exécution qui est responsable de l'allocation de mémoire, de la même manière que vous pouvez supprimer un tableau créé avec malloc dans la norme C en utilisant libre. Je pense que chaque compilateur implémente différemment. Une façon courante consiste à allouer une cellule supplémentaire pour la taille du tableau.

Cependant, le moteur d'exécution est pas assez intelligent pour détecter si oui ou non il est un tableau ou un pointeur, vous devez l'informer, et si vous vous trompez, soit vous ne supprimez pas correctement (par exemple, ptr au lieu de tableau ), ou vous finissez par prendre une valeur sans rapport avec la taille et causer des dommages importants.

L'une des approches pour compilateurs est d'allouer un peu plus compte de la mémoire et de stocker des éléments dans l'élément de tête.

Exemple comment cela pourrait se faire: Ici

int* i = new int[4];

compilateur allouer sizeof (int) * 5 octets.

int *temp = malloc(sizeof(int)*5)

stockera 4 dans les premiers octets de sizeof(int)

*temp = 4;

et i set

i = temp + 1;

Donc les points i à matrice de 4 éléments, pas 5.

delete[] i;

sera traité après façon

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)

sémantiquement, les deux versions de l'opérateur de suppression en C ++ peut « manger » tout pointeur; cependant, si un pointeur vers un objet unique est donnée à delete[], puis UB entraînera, ce qui signifie tout peut arriver, y compris un plantage du système ou rien du tout.

C ++ nécessite le programmeur de choisir la bonne version de l'opérateur de suppression en fonction du sujet de désaffectation. Tableau ou objet unique

Si le compilateur peut déterminer automatiquement si un pointeur transmis à l'opérateur de suppression est un tableau de pointeur, alors il n'y aurait qu'un seul opérateur delete en C ++, ce qui suffirait pour les deux cas.

D'accord que le compilateur ne sait pas si elle est un tableau ou non. Il est au programmeur.

Le compilateur parfois garder une trace de combien d'objets doivent être supprimés de plus-allocation suffisante pour stocker la taille du tableau, mais pas toujours nécessaire.

Pour une spécification complète lorsque le stockage supplémentaire est alloué, s'il vous plaît se référer à C ++ ABI (comment compilateurs sont mis en œuvre): C ++ ABI Itanium: Array opérateur de nouveaux cookies

Vous ne pouvez pas utiliser supprimer pour un tableau, et vous ne pouvez pas utiliser supprimer [] pour un non-tableau.

« comportement non défini » signifie simplement la spécification de langage ne fait aucune gaurantees quant à ce qui va se passer. Cela ne signifie pas nessacerally que quelque chose va se passer.

  

dont le travail est-il de se rappeler ces choses? Est-ce que le système d'exploitation garder un certain type d'enregistrement en arrière-plan? (Je veux dire, je me rends compte que j'ai commencé ce post en disant que ce qui se passe est défini, mais le fait est, le scénario ne se produit pas, si quelqu'un donc dans le monde pratique « tuerie » se souvient.)

Il y a généralement deux couches ici. Le gestionnaire de mémoire sous-jacente et l'implémentation C ++.

En général, le gestionnaire de mémoire se souviendront (entre autres) la taille du bloc de mémoire qui a été allouée. Cela peut être plus grand que le bloc C ++ la mise en œuvre a demandé. En général, le gestionnaire de mémoire stocke ses métadonnées avant le bloc de mémoire alloué.

La mise en œuvre C ++ se souviendra généralement que la taille du tableau si elle doit le faire pour ses propres fins, généralement parce que le type a une destructor non trival.

pour les types avec un destructor trivial la mise en œuvre de « supprimer » et « supprimer [] » est généralement le même. La mise en œuvre C de passe simplement le pointeur vers le gestionnaire de mémoire sous-jacente. Quelque chose comme

free(p)

Par contre pour les types avec un destructeur non trivial « supprimer » et « supprimer [] » sont susceptibles d'être différents. serait somthing comme "delete" (où T est le type que les points de pointeur)

p->~T();
free(p);

Alors que "supprimer []" serait quelque chose comme.

size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
  p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);

Hé ho bien cela dépend de ce que vous avec l'allocation nouvelle [] expression lorsque vous allouez tableau de construction dans les types ou classes / structure et vous ne fournissez pas votre constructeur et Destructeur l'opérateur traitera comme une taille « sizeof ( objet) * numObjects » plutôt que tableau d'objets donc dans ce numéro de cas d'objets attribués ne seront pas stockés nulle part, si vous allouez tableau d'objets et de vous fournir constructeur et destructor dans votre objet que le changement de comportement, une nouvelle expression attribuera 4 octets de plus en magasin nombre d'objets dans les 4 premiers octets de sorte que le destructor pour chacun d'entre eux peut être appelé et donc nouveau [] expression sera pointeur de retour décalé de 4 octets avant, que lorsque la mémoire est retournée la supprimer [] l'expression appellera un modèle de fonction Tout d'abord, itérer sur tableau d'objets et d'appeler un destructeur pour chacun d'eux. J'ai créé cette surchargent simple, sorcière nouveau code [] et supprimer [] expressions et fournit une fonction de modèle pour désaffecter la mémoire et appeler destructor pour chaque objet si nécessaire:

// overloaded new expression 
void* operator new[]( size_t size )
{
    // allocate 4 bytes more see comment below 
    int* ptr = (int*)malloc( size + 4 );

    // set value stored at address to 0 
    // and shift pointer by 4 bytes to avoid situation that
    // might arise where two memory blocks 
    // are adjacent and non-zero
    *ptr = 0;
    ++ptr; 

    return ptr;
}
//////////////////////////////////////////

// overloaded delete expression 
void static operator delete[]( void* ptr )
{
    // decrement value of pointer to get the
    // "Real Pointer Value"
    int* realPtr = (int*)ptr;
    --realPtr;

    free( realPtr );
}
//////////////////////////////////////////

// Template used to call destructor if needed 
// and call appropriate delete 
template<class T>
void Deallocate( T* ptr )
{
    int* instanceCount = (int*)ptr;
    --instanceCount;

    if(*instanceCount > 0) // if larger than 0 array is being deleted
    {
        // call destructor for each object
        for(int i = 0; i < *instanceCount; i++)
        {
            ptr[i].~T();
        }
        // call delete passing instance count witch points
        // to begin of array memory 
        ::operator delete[]( instanceCount );
    }
    else
    {
        // single instance deleted call destructor
        // and delete passing ptr
        ptr->~T();
        ::operator delete[]( ptr );
    }
}

// Replace calls to new and delete
#define MyNew ::new
#define MyDelete(ptr) Deallocate(ptr)

// structure with constructor/ destructor
struct StructureOne
{
    StructureOne():
    someInt(0)
    {}
    ~StructureOne() 
    {
        someInt = 0;
    }

    int someInt;
};
//////////////////////////////

// structure without constructor/ destructor
struct StructureTwo
{
    int someInt;
};
//////////////////////////////


void main(void)
{
    const unsigned int numElements = 30;

    StructureOne* structOne = nullptr;
    StructureTwo* structTwo = nullptr;
    int* basicType = nullptr;
    size_t ArraySize = 0;

/**********************************************************************/
    // basic type array 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( int ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor. value assigned to basicType pointer
    // will be the same as value of "++ptr" in new expression
    basicType = MyNew int[numElements];

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( int ) * numElements"
    MyDelete( basicType );

/**********************************************************************/
    // structure without constructor and destructor array 

    // behavior will be the same as with basic type 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( StructureTwo ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor value assigned to structTwo pointer
    // will be the same as value of "++ptr" in new expression
    structTwo = MyNew StructureTwo[numElements]; 

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( StructureTwo ) * numElements"
    MyDelete( structTwo );

/**********************************************************************/
    // structure with constructor and destructor array 

    // place break point check size and compare it with size passed in
    // new expression size in expression will be larger by 4 bytes
    ArraySize = sizeof( StructureOne ) * numElements;

    // value assigned to "structOne pointer" will be different 
    // of "++ptr" in new expression  "shifted by another 4 bytes"
    structOne = MyNew StructureOne[numElements];

    // Place break point in template function to see the behavior
    // destructors will be called for each array object 
    MyDelete( structOne );
}
///////////////////////////////////////////

définir juste un destructor dans une classe et d'exécuter votre code à la fois la syntaxe

delete pointer

delete [] pointer

en fonction de la sortie u peut trouver les solutions

La réponse:

int * Parray = new int [5];

int size = * (Parray-1);

Publié ci-dessus n'est pas correct et produit valeur non valide.  Le « -1 » compte des éléments Sur 64 bits systèmes d'exploitation Windows la taille de la mémoire tampon correcte réside dans Ptr - 4 octets adresse

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top