Frage

Ist es tatsächlich möglich, Placement New in portablem Code zu nutzen, wenn man ihn für Arrays verwendet?

Es scheint, dass der Zeiger, den Sie von new[] zurückerhalten, nicht immer mit der Adresse übereinstimmt, die Sie übergeben (5.3.4, Anmerkung 12 im Standard scheint zu bestätigen, dass dies korrekt ist), aber ich verstehe nicht, wie Sie In diesem Fall kann dem Array ein Puffer zugewiesen werden.

Das folgende Beispiel zeigt das Problem.Dieses mit Visual Studio kompilierte Beispiel führt zu einer Speicherbeschädigung:

#include <new>
#include <stdio.h>

class A
{
    public:

    A() : data(0) {}
    virtual ~A() {}
    int data;
};

int main()
{
    const int NUMELEMENTS=20;

    char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
    A *pA = new(pBuffer) A[NUMELEMENTS];

    // With VC++, pA will be four bytes higher than pBuffer
    printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);

    // Debug runtime will assert here due to heap corruption
    delete[] pBuffer;

    return 0;
}

Wenn man sich den Speicher ansieht, scheint der Compiler die ersten vier Bytes des Puffers zu verwenden, um die Anzahl der darin enthaltenen Elemente zu speichern.Dies bedeutet, dass der Puffer nur vorhanden ist sizeof(A)*NUMELEMENTS groß, das letzte Element im Array wird in den nicht zugewiesenen Heap geschrieben.

Die Frage ist also: Können Sie herausfinden, wie viel zusätzlichen Overhead Ihre Implementierung benötigt, um die Platzierung von new[] sicher zu nutzen?Idealerweise benötige ich eine Technik, die zwischen verschiedenen Compilern portierbar ist.Beachten Sie, dass zumindest im Fall von VC der Overhead für verschiedene Klassen unterschiedlich zu sein scheint.Wenn ich beispielsweise den virtuellen Destruktor im Beispiel entferne, ist die von new[] zurückgegebene Adresse dieselbe wie die Adresse, die ich übergebe.

War es hilfreich?

Lösung

Persönlich würde ich mich für die Option entscheiden, „Placement New“ nicht für das Array zu verwenden und stattdessen „Placement New“ für jedes Element im Array einzeln zu verwenden.Zum Beispiel:

int main(int argc, char* argv[])
{
  const int NUMELEMENTS=20;

  char *pBuffer = new char[NUMELEMENTS*sizeof(A)];
  A *pA = (A*)pBuffer;

  for(int i = 0; i < NUMELEMENTS; ++i)
  {
    pA[i] = new (pA + i) A();
  }

  printf("Buffer address: %x, Array address: %x\n", pBuffer, pA);

  // dont forget to destroy!
  for(int i = 0; i < NUMELEMENTS; ++i)
  {
    pA[i].~A();
  }    

  delete[] pBuffer;

  return 0;
}

Unabhängig von der Methode, die Sie verwenden, stellen Sie sicher, dass Sie jedes dieser Elemente im Array manuell zerstören, bevor Sie pBuffer löschen, da es sonst zu Lecks kommen könnte ;)

Notiz:Ich habe das nicht kompiliert, aber ich denke, es sollte funktionieren (ich arbeite auf einem Rechner, auf dem kein C++-Compiler installiert ist).Es zeigt immer noch, worauf es ankommt :) Ich hoffe, es hilft irgendwie!


Bearbeiten:

Der Grund, warum es die Anzahl der Elemente im Auge behalten muss, besteht darin, dass es diese durchlaufen kann, wenn Sie delete für das Array aufrufen, und sicherstellen, dass die Destruktoren für jedes der Objekte aufgerufen werden.Wenn es nicht wüsste, wie viele es sind, wäre es dazu nicht in der Lage.

Andere Tipps

@Derek

In Abschnitt 5.3.4, Abschnitt 12 geht es um den Overhead bei der Array-Zuordnung, und wenn ich ihn nicht falsch verstehe, scheint es mir nahezulegen, dass es für den Compiler gültig ist, ihn auch bei der Neuplatzierung hinzuzufügen:

Dieser Overhead kann in allen Array-Neuausdrücken angewendet werden, einschließlich derjenigen, die auf den Bibliotheksfunktionsoperator new[](std::size_t, void*) und andere Platzierungszuordnungsfunktionen verweisen.Die Höhe des Overheads kann von einem Aufruf von new zum anderen variieren.

Allerdings glaube ich, dass VC neben GCC, Codewarrior und ProDG der einzige Compiler war, der mir damit Probleme bereitet hat.Ich müsste allerdings noch einmal nachschauen, um sicherzugehen.

Danke für die Antworten.Die Lösung, die ich schließlich verwendet habe, als ich darauf stieß, war die Verwendung einer neuen Platzierung für jedes Element im Array (leider hätte ich das in der Frage erwähnen sollen).Ich hatte einfach das Gefühl, dass mir bei der Umsetzung mit „placement new[“ etwas gefehlt haben muss.So wie es aussieht, scheint die Platzierung new[] im Wesentlichen unbrauchbar zu sein, da der Standard es dem Compiler ermöglicht, dem Array einen zusätzlichen, nicht spezifizierten Overhead hinzuzufügen.Ich verstehe nicht, wie Sie es jemals sicher und tragbar verwenden könnten.

Mir ist nicht einmal wirklich klar, warum die zusätzlichen Daten benötigt werden, da Sie delete[] für das Array sowieso nicht aufrufen würden. Daher verstehe ich nicht ganz, warum es wissen muss, wie viele Elemente darin enthalten sind.

@James

Mir ist nicht einmal wirklich klar, warum die zusätzlichen Daten benötigt werden, da Sie delete[] für das Array sowieso nicht aufrufen würden. Daher verstehe ich nicht ganz, warum es wissen muss, wie viele Elemente darin enthalten sind.

Nachdem ich darüber nachgedacht habe, stimme ich Ihnen zu.Es gibt keinen Grund, warum bei der Platzierung „Neu“ die Anzahl der Elemente gespeichert werden sollte, da es keine Platzierung „Löschen“ gibt.Da es kein Placement-Delete gibt, gibt es keinen Grund für Placement New, um die Anzahl der Elemente zu speichern.

Ich habe dies auch mit gcc auf meinem Mac getestet und dabei eine Klasse mit einem Destruktor verwendet.Auf meinem System war Platzierung neu nicht Ändern des Zeigers.Daher frage ich mich, ob es sich hierbei um ein VC++-Problem handelt und ob dies möglicherweise gegen den Standard verstößt (soweit ich das beurteilen kann, geht der Standard nicht speziell darauf ein).

Placement new selbst ist portierbar, aber die Annahmen, die Sie darüber treffen, was es mit einem bestimmten Speicherblock macht, sind nicht portierbar.Wie bereits gesagt: Wenn Sie ein Compiler wären und einen Teil des Speichers hätten, wie würden Sie dann wissen, wie Sie ein Array zuweisen und jedes Element ordnungsgemäß zerstören, wenn Sie nur einen Zeiger hätten?(Siehe die Schnittstelle des Operators delete[].)

Bearbeiten:

Und es gibt tatsächlich ein Placement-Delete, nur wird es nur dann aufgerufen, wenn ein Konstruktor eine Ausnahme auslöst, während er ein Array mit der Placement-New[] zuweist.

Ob new[] tatsächlich irgendwie die Anzahl der Elemente im Auge behalten muss, bleibt dem Standard überlassen, der es dem Compiler überlässt.In diesem Fall leider.

Ich denke, gcc macht dasselbe wie MSVC, aber das macht es natürlich nicht „portabel“.

Ich denke, Sie können das Problem umgehen, wenn NUMELEMENTS tatsächlich eine Kompilierzeitkonstante ist, etwa so:

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

Dies sollte die neue Skalarplatzierung verwenden.

Ähnlich wie Sie ein einzelnes Element verwenden würden, um die Größe für eine neue Platzierung zu berechnen, verwenden Sie ein Array dieser Elemente, um die für ein Array erforderliche Größe zu berechnen.

Wenn Sie die Größe für andere Berechnungen benötigen, bei denen die Anzahl der Elemente möglicherweise nicht bekannt ist, können Sie sizeof(A[1]) verwenden und mit der erforderlichen Elementanzahl multiplizieren.

z.B

char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ];
A *pA = (A*)pBuffer;

for(int i = 0; i < NUMELEMENTS; ++i)
{
    pA[i] = new (pA + i) A();
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top