Frage

In Ordnung, ich denke, wir stimmen alle darin überein, dass, was mit dem folgenden Code geschieht, ist nicht definiert, je nachdem, was passiert ist,

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

Der Zeiger könnten alle Arten von verschiedenen Dingen, und so weiter es eine unbedingte delete[] Durchführung ist nicht definiert. Allerdings gehen wir davon aus, dass wir in der Tat ein Array Zeiger sind vorbei,

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

Meine Frage ist, in diesem Fall, in dem der Zeiger ist ein Array ist, wer ist es, das weiß? Ich meine, aus der Sprache / Compiler Sicht, es hat keine Ahnung, ob oder ob nicht arr ist ein Array Zeiger im Vergleich zu einem Zeiger auf einen einzigen int. Heck, es nicht einmal weiß, ob arr dynamisch erstellt wurde. Doch wenn ich die folgende Stelle,

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

Das Betriebssystem ist intelligent genug, um nur ein int zu löschen und nicht auf irgendeine Art von ‚Amoklauf‘ geht von über diesen Punkt hinaus den Rest des Speichers zu löschen (Kontrast, mit strlen und einem nicht-\0 terminiert String - es halten zu gehen, bis er trifft 0).

So, deren Aufgabe es ist, diese Dinge zu erinnern? Ist das Betriebssystem eine Art Aufzeichnung im Hintergrund halten? (Ich meine, ich weiß, dass ich diesen Beitrag begann damit, dass das, was passiert ist, nicht definiert, aber die Tatsache ist, das ‚Amoklauf‘ -Szenario nicht geschieht, so deshalb in der praktischen Welt jemand ist Erinnern.)

War es hilfreich?

Lösung

Der Compiler weiß nicht, es ein Array ist, es dem Programmierer ist zu vertrauen. dazu führen würde, nicht definiertes Verhalten einen Zeiger auf eine einzelne int mit delete [] löschen. Ihr zweites main() Beispiel ist unsicher, auch wenn sie nicht unmittelbar zum Absturz bringen.

Der Compiler zu halten hat verfolgen, wie viele Objekte müssen irgendwie gelöscht werden. Es kann dies tun, indem über Zuweisung genug, um die Array-Größe zu speichern. Weitere Details, die C ++ Super-FAQ .

Andere Tipps

Eine Frage, dass die Antworten bisher nicht gegeben zu adressieren scheinen: Wenn die Laufzeitbibliotheken (nicht das O, wirklich) Überblick über die Anzahl der Dinge in der Reihe halten können, warum dann brauchen wir die delete[] Syntax an alle? Warum kann nicht eine einzige delete Form verwendet werden, um alle Löschungen zu behandeln?

Die Antwort auf diese geht zurück auf C ++ 's Wurzeln als C-kompatible Sprache (die es nicht mehr bemüht sich wirklich zu sein.) Stroustrup Philosophie war, dass der Programmierer sollte für alle Funktionen, die nicht zahlen müssen, die sie nicht verwenden. Wenn sie nicht Arrays verwenden, dann sollten sie die Kosten für das Objekt-Arrays für jeden zugewiesenen Teil des Speichers nicht zu tragen haben.

Das heißt, wenn Sie den Code einfach funktioniert

Foo* foo = new Foo;

dann der Speicherplatz, der für foo zugeordnet ist nicht enthält keinen zusätzlichen Aufwand, der erforderlich wäre, Anordnungen von Foo zu unterstützen.

Da nur Array Zuweisungen eingestellt werden, um die zusätzliche Anordnung Größeninformationen zu tragen, müssen Sie dann die Laufzeitbibliotheken sagen, für diese Informationen zu suchen, wenn Sie die Objekte löschen. Deshalb haben wir verwenden müssen

delete[] bar;

anstelle von nur

delete bar;

, wenn bar ist ein Zeiger auf ein Feld.

Für die meisten von uns (mich eingeschlossen), die Umständlichkeit über ein paar zusätzliche Bytes Speicher scheint in diesen Tagen ruhig. Aber es gibt noch einige Situationen, in denen ein paar Bytes speichern (von dem, was eine sehr hohe Anzahl von Speicherblöcken sein könnte) kann wichtig sein.

Ja, hält das Betriebssystem einige Dinge im ‚Hintergrund‘. Zum Beispiel, wenn Sie ausführen

int* num = new int[5];

kann das OS 4 zusätzlichen Bytes zuzuteilen, speichern die Größe der Zuordnung in den ersten 4 Bytes des zugewiesenen Speicher und Rück einen Offsetzeiger (das heißt, weist sie Speicherplätze 1000 bis 1024, aber der Zeiger zurück Punkte bis 1004, mit Standorte 1000-1003 die Größe der Zuordnung zu speichern). Wenn dann löschen genannt wird, ist es bei 4 Bytes aussehen kann, bevor der Zeiger an sie übergeben die Größe der Zuordnung zu finden.

Ich bin sicher, dass es andere Möglichkeiten, um die Größe einer Zuteilung Tracking, aber das ist eine Option.

Dies ist sehr ähnlich wie this Frage und es viele Details hat Ihr für suchen.

Aber es genügt zu sagen, es ist nicht die Aufgabe des OS irgendetwas davon zu verfolgen. Es ist eigentlich die Laufzeitbibliotheken oder die zugrunde liegenden Speichermanager, der die Größe des Arrays verfolgen wird. Dies wird in der Regel durch die Zuweisung zusätzlicher Speicher vorne erfolgt und Speichern der Größe der Anordnung in dieser Position (die meisten verwenden, um einen Kopfknoten).

Dies ist sichtbar auf einigen Implementierungen durch den folgenden Code ausführen

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

delete oder delete[] würden beide vermutlich den zugewiesenen Speicher frei (Speicher zeigte), aber der große Unterschied ist, dass delete auf einem Array das destructor jedes Element des Arrays nicht nennen.

Wie auch immer, new/new[] und delete/delete[] Mischen ist wahrscheinlich UB.

Es weiß nicht, es ein Array ist, das ist, warum Sie delete[] statt regelmäßigen alten delete zu versorgen haben.

Ich hatte eine ähnliche Frage zu diesem. In C, weisen Sie Speicher mit malloc () (oder einer ähnlichen Funktion), und löschen Sie es mit free (). Es gibt nur eine malloc (), die nur eine bestimmte Anzahl von Bytes zuordnet. Es gibt nur eine freie (), die einfach einen Zeiger nimmt, wie es Parameter ist.

Also, warum es ist, dass in C können Sie einfach den Zeiger übergeben zu befreien, aber in C ++ müssen Sie es sagen, ob es sich um ein Array oder eine einzelne Variable?

Die Antwort, ich habe gelernt, hat mit Klasse Destruktoren zu tun.

Wenn Sie eine Instanz einer Klasse zuweisen MyClass ...

classes = new MyClass[3];

Und es mit Lösch löschen, können Sie nur den Destruktor für die erste Instanz von MyClass aufgerufen. Wenn Sie löschen Verwenden Sie [], können Sie sicher sein, dass der Destruktor für alle Instanzen in dem Array aufgerufen wird.

Das ist der entscheidende Unterschied. Wenn Sie sich einfach mit Standardtypen (zum Beispiel int) arbeiten, werden Sie nicht wirklich um dieses Problem zu sehen. Außerdem sollten Sie dieses Verhalten erinnern für neue löschen Verwendung auf [] und delete [] auf neue nicht definiert ist -. Es kann nicht die gleiche Art und Weise funktionieren auf jedem Compiler / System

Es ist an der Laufzeit, die für die Speicherzuweisung verantwortlich ist, in der gleichen Weise, dass Sie ein Array mit malloc in Standard C mit freien erstellt löschen. Ich denke, dass jeder Compiler es anders implementiert. Ein üblicher Weg ist es, eine zusätzliche Zelle für die Array-Größe zuzuordnen.

Allerdings ist die Laufzeit nicht intelligent genug, um zu erkennen, ob es sich um ein Array oder ein Zeiger, müssen Sie ihn informieren, und wenn Sie sich irren, Sie entweder löschen nicht korrekt (ZB ptr statt Array ), oder Sie einen unabhängigen Wert für die Größe am Ende unter und erhebliche Schäden verursachen.

Ein Ansatz für Compiler ist ein wenig mehr Speicher und speichert Anzahl der Elemente in dem Kopfelement zuzuordnen.

Beispiel, wie es getan werden könnte: Hier

int* i = new int[4];

Compiler sizeof (int) * 5 Bytes zuzuordnen.

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

Wird speichern 4 in erster sizeof(int) Bytes

*temp = 4;

ein und i

i = temp + 1;

So i Punkte auf Anordnung von vier Elementen, nicht 5.

Und

delete[] i;

wird wie folgt verarbeitet werden

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)

Semantisch, beide Versionen von Delete-Operator in C ++ kann "essen" jeder Zeiger; jedoch, wenn ein Zeiger auf ein einzelnes Objekt gegeben wird delete[], dann UB führen, alles was bedeutet, kann passieren, einschließlich eines Systemabsturzes oder gar nichts.

C ++ den Programmierer erfordert die richtige Version des Löschbetreiber zu wählen, je nach Thema Deallokation. Array oder einzelnes Objekt

Wenn der Compiler automatisch, ob ein Zeiger auf den Delete-Operator übergeben bestimmen könnte ein Zeigerfeld war, dann wäre es nur eine delete-Operator in C ++ sein, die für beide Fälle ausreichen würde.

Einig, dass der Compiler nicht weiß, ob es ein Array ist oder nicht. Es liegt an den Programmierer.

Der Compiler hält manchmal verfolgen, wie viele Objekte müssen durch über Zuweisung genug gelöscht werden, um die Array-Größe zu speichern, aber nicht immer notwendig.

Für eine vollständige Spezifikation, wenn zusätzliche Speicher zugeordnet werden, finden Sie in C ++ ABI (wie Compiler implementiert werden): Itanium C ++ ABI: Array Operator neue Cookies

Sie können nicht verwenden löschen für ein Array, und Sie können nicht verwenden delete [] für einen Nicht-Array.

„undefiniertes Verhalten“ bedeutet einfach die Sprache spec keine gaurantees zu dem macht, was passieren wird. Dabei spielt es keine nessacerally bedeuten, dass etwas Schlimmes passieren wird.

  

So, deren Aufgabe es ist, diese Dinge zu erinnern? Ist das Betriebssystem eine Art Aufzeichnung im Hintergrund halten? (Ich meine, ich weiß, dass ich diesen Beitrag begann damit, dass das, was passiert ist, nicht definiert, aber die Tatsache ist, die ‚Killing Spree‘ -Szenario nicht geschieht, so deshalb in der praktischen Welt jemand erinnern.)

Es ist in der Regel zwei Schichten hier. Die zugrunde liegenden Speicher-Manager und die C ++ Implementierung.

In der Regel wird der Speicher-Manager erinnern (unter anderem) die Größe des Blocks des Speichers, der zugeordnet wurde. Dies kann größer sein als der Block die C ++ Implementierung gefragt. Typischerweise wird der Speicher-Manager speichert es Metadaten vor dem zugewiesenen Speicherblock.

Die C ++ Implementierung wird nur im Allgemeinen die Größe des Arrays erinnern, ob es für seine eigenen Zwecke so tun muss, in der Regel, weil die Art, die einen nicht-trivialen destructor hat.

für Typen mit einem trivialen destructor So ist die Implementierung von „Löschen“ und „delete []“ ist in der Regel gleich. Die C ++ Implementierung einfach übergibt den Zeiger auf den zugrunde liegenden Speicher-Manager. So etwas wie

free(p)

Auf der anderen Seite für die Typen mit einem nicht-trivial destructor „löscht“ und „delete []“ sind wahrscheinlich anders zu sein. „Löschen“ wäre ungefähr wie (wobei T die Art, dass der Zeiger auf)

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

Während "delete []" so etwas wie sein würde.

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);

Hey ho gut es hängt davon ab, was man mit neuen [] Ausdruck Zuweisung, wenn Sie Array von Build-in-Typen oder Klasse / Struktur zuordnen und Sie bieten nicht Ihre und Destruktor der Betreiber als eine Größe „sizeof behandeln ( Gegenstand) * numObjects“und nicht als Objekt Array daher in diesem Fall Anzahl der zugeordneten Objekte werden nicht überall gespeichert werden, aber wenn Sie Objektarray zuweisen und Sie bieten und Destruktor in Ihrem Objekt als Verhaltensänderung, wird neuer Ausdruck 4 Bytes mehr zuteilen und store Anzahl der Objekte in ersten 4 Bytes so Destruktor für jeden von ihnen genannt und daher neu sein kann [] Ausdruck wird vorwärts durch 4 Bytes Zeiger verschoben zurück, als wenn der Speicher die delete [] zurückgegeben wird, wird die Expression einer Funktion Vorlage nennen zunächst durchlaufen Array von Objekten, und für jeden von ihnen destructor nennen. Ich habe diese einfache Code Hexe erstellt neue Überlastungen [] und delete [] Ausdrücke und stellt eine Template-Funktion Speicher freizugeben und für jedes Objekt Destruktoraufrufs, wenn nötig:

// 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 );
}
///////////////////////////////////////////

definiert nur einen Destruktor in einer Klasse und führen Sie den Code sowohl mit Syntax

delete pointer

delete [] pointer

nach der Ausgabe u die Lösungen finden

Die Antwort:

int * pArray = new int [5];

int size = * (pArray-1);

Veröffentlicht ist oben nicht korrekt und erzeugt ungültigen Wert.  Die „-1“ zählt Elemente Auf 64-Bit-Betriebssystem Windows die richtige Puffergröße in Ptr residiert - 4 Byte-Adresse

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top