Krishty hat geschrieben:Dabei gibt das new unter bestimmten Umständen (siehe Link) eine andere Adresse zurück als übergeben wurde. Das ist nun der Streitpunkt – ob new das darf oder nicht.
tl;dr:
new darf das imho definitiv. Abgesehen davon hat der Code in diesem Bugreport Undefined Behavior. Sogar dann noch, wenn wir das
new komplett aus der Betrachtung nehmen.
Grund für die ganze Verwirrung sind wohl einige Missverständnisse was Terminologie betrifft. Fangen wir an mit dem Beispiel Operator Overloading, wo es gerne zu ähnlichen Verwechslungen kommt: Bei einer Funktion mit Namen
operator + handelt es sich
nicht um den + Operator. Der + Operator ist der + Operator. Der + Operator ist ein Syntaxelement einer
additive-expression. Eine Funktion mit Namen
operator + ist eine
operator function. Der Compiler ruft als Teil der Evaluierung einer entsprechenden Expression mit entsprechenden Operanden in bestimmten Fällen eine operator function auf. Die operator function
ist aber
nicht der Operator. Der Operator
verwendet die operator function.
Wesentlich für den konkreten Fall hier ist die Unterscheidung zwischen einer
new-expression und einer
allocation function bzw.
deallocation function. So etwas wie
new int ist eine
new-expression. Eine
new-expression erzeugt Objekte. Die Evaluierung einer
new-expression kann (seit C++14 mit Betonung auf
kann) eine allocation function aufrufen um Speicher für die zu erzeugenden Objekte zu besorgen. Eine allocation function bzw. deallocation function ist eine Funktion mit einer
declarator-id der Form
operator new oder
operator new[] bzw.
operator delete oder
operator delete[]. Eine
new-expression liefert einen Pointer auf das erzeugte Objekt bzw. einen Pointer auf das erste Element im Falle dass das erzeugte Objekt ein Array ist. Es gibt keine expliziten Garantien bezüglich eines Zusammenhangs zwischen dem Resultat einer
new-expression und einem Pointer der aus dem Aufruf einer allocation function kam (abgesehen von
einer Ausnahme die hier nicht Relevant ist).
Eine
new-expression kann ein
new-placement enthalten. Ein
new-placement gibt eine Liste von Expressions die als zusätzliche Parameter an die allocation function übergeben werden. Eine
new-expression die ein
new-placement enthält wird als
placement new-expression bezeichnet.
new(tab) Ptr[3]{} ist eine
placement new-expression. Nicht weil hier konzeptionell ein Objekt in einem vorhandenen Objekt "platziert" wird, sondern weil es sich um eine
new-expression mit einem
new-placement handelt.
new (1, 2, 3) int; ist auch eine
placement new-expression. (Die Bezeichnung kommt ursprünglich vermutlich aber wohl von der Benutzung im Zusammenhang mit dem "Platzieren" von Objekten.)
Der Standard
definiert eine Reihe von allocation und deallocation functions als Teil der Standard Library. Dabei wird zwischen single-object, array und non-allocating forms unterschieden.
void* operator new(std::size_t size, void* ptr) noexcept ist eine der standard allocation functions die zu den
non-allocating forms gehören. Non-allocating eben weil diese allocation functions keinen neuen Speicher allokieren, sondern in der Tat
per Definition einfach nur den übergebenen Pointer returnen. Unsere
placement-new expression new(tab) Ptr[3]{} ruft genau ebendiese non-allocating allocation function (ja, den Term muss man sich auf der Zunge zergehen lassen) auf um sich den Speicher für das zu erzeugende Array zu besorgen. Und die allocation function returned einfach den übergebenen Pointer und die new-expression konstruiert das zu konstruierende Objekt dann im Speicher auf den dieser Pointer zeigt. Und das alles läuft am Ende dann darauf hinaus, dass unser Array tatsächlich in die gegebene Storage "platziert" wird.
An dieser Stelle wird klar: Man kann es einem nicht übel nehmen wenn man sich in dieser Terminologie, vermutlich ohne es zu merken, etwas verirrt. (
Wie wir alle wissen, hat C++ hat ja einen merkwürdigen Hang dazu, dass alle Terminologie auf einmal komplett schiefläuft, wann immer es ans Allokieren geht…)
Aber nun zur eigentlichen Frage:
Krishty hat geschrieben:
Der Support-Chinese behauptet:
The standard only requires allocation function of non-allocating form to return the original pointer. See [new.delete.placement].
In your example, 'new (tab) Ptr[3]{}' is a placement new-expression which calls allocation function and does object initialization. In this case, the standard says (see [expr.new]):
When the allocation function returns a value other than null, it must be a pointer to a block of storage
in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned
and of the requested size. The address of the created object will not necessarily be the same as that of the
block if the object is an array.
So even if the allocation function returns the original pointer, the address of the created object can be different from it.
Ich verstehe den Unterschied zwischen
Allocating Form und
non-Allocating Form leider nicht.
„'new (tab) Ptr[3]{}' is a placement new-expression which calls allocation function“ klingt für mich völlig falsch; welches Placement
new ruft denn eine Allocation Funktion auf?! Hat jemand einen C++-Standard zur Hand, um das zu klären?
Der "Support-Chinese"
ist ein Compiler Engineer und hat recht. ;) Während die non-allocating allocation functions in der Tat den übergebenen Pointer unverändert returnen, gibt es keine solche Garantie für die
new-expression welche diese allocation function aufruft. Der Standard vermerkt diesen Umstand sogar explizit
in einer Note, welche Herr Xiang Fan auch korrekt zitiert:
[expr.new] §18 hat geschrieben:[…] The address of the created object will not necessarily be the same as that of the block if the object is an array.
Schauen wir uns den fraglichen Code mal an:
Code: Alles auswählen
Ptr* tab = reinterpret_cast<Ptr*>(data);
new(tab) Ptr[3]{};
for (int i = 0; i < 3; i++)
std::cout << tab[i].handle << std::endl;
Eigentlich geht es hier um etwas anderes als die Frage was genau diese
placement new-expression zurückliefert. Wie sollte es auch? Das Resultat der Expression wird doch völlig ignoriert!? Dieser Code geht davon aus dass
new(tab) Ptr[3]{} ein Array von drei
Ptr Objekten an Adresse
tab konstruiert. Ja, man könnte einen Haufen Ausnahmen einführen, die z.B. im Fall von Array mit konstanter Größe, trivialem Elementtyp und Verwendung einer non-allocating allocation function eine solche Garantie abgibt. Aber wäre es das wirklich Wert? Der
new Kram ist imo so schon kompliziert genug. Im Allgemeinen muss die
new-expression mit einer dynamischen Anzahl an Elementen zurechtkommen. Im Allgemeinen muss die
new-expression damit zurechtkommen, dass an irgendeinem Punkt in der Konstruktion des Array eine Exception fliegt und alle bisher konstruierten Elemente in umgekehrter Reihenfolge zerstört werden müssen. Im Allgemeinen muss eine passende
delete-expression nur basierend auf dem von einer
new-expression gelieferten Pointer das Array zerstören können. Irgendwo muss der Compiler da Information über die Anzahl der konstruierten Objekte in diesem Array unterbringen. Der einzige Speicher der ihm zur Verfügung steht ist der auf den
tab zeigt. Der Compiler hat im Allgemeinen keine Möglichkeit, die Größe einer Allocation abzufragen, die Information ans Ende packen ist also auch nicht wirklich eine Option. Eine Garantie dass eine solche
new-expression das erste Element an der gegebenen Adresse konstruiert, würde eine sinnvolle Implementierung im Allgemeinen also effektiv unmöglich machen (würde erfordern dass der Compiler zusätzlich interne Datenstrukturen unbeschränkter Größe anlegt um Allocations zu tracken). Genau darum gibt es keine solche Garantie. Der Compiler war hier allerdings zumindest smart genug, zu checken, dass er sich all das sparen kann so lange
Ptr einen trivialen Destruktor hat. Darum gehen die Dinge in der Praxis hier erst schief sobald
Ptr keinen trivialen Destruktor mehr hat…
Übrigens, selbst wenn ich den Code so umschreibe:
Code: Alles auswählen
Ptr data[3];
Ptr* tab = reinterpret_cast<Ptr*>(&data);
for (int i = 0; i < 3; i++)
std::cout << tab[i].handle << std::endl;
ist
tab kein gültiger Zeiger in ein Array und ein Lesen von
tab damit Undefined Behavior. Ein Array und das erste Element eines Arrays sind nicht pointer interconvertible. Mit anderen Worten: Es ist per Definition nicht möglich aus der Adresse eines Array einen gültigen Pointer auf das erste Element abzuleiten…
Den aktuellen Draft des C++ Standard schön als HTML aufbereitet gibt's übrigens immer hier: http://eel.is/c++draft/
MasterQ32 hat geschrieben:cppreference.com sagt:
cppreference.com hat geschrieben:If placement_params are provided, they are passed to the allocation function as additional arguments. Such allocation functions are known as "placement new", after the standard allocation function void* operator new(std::size_t, void*), which simply returns its second argument unchanged.
Das sagt wohl, dass das placement new mit Adresse diese Adresse zurückgeben muss, man kann aber auch diesen Operator überladen...
Nope, diese allocation function ist nicht replaceable… ;)
PS: Was wirklich cool wäre, wäre Syntax Highlighting für inline Code…
PPS: Die beiden
Code: Alles auswählen
Blöck da oben werfen irgendwie die Formatierung komplett aus der Bahn. Interessanterweise offenbar nur wenn sie in exakt dieser Form an bestimmten Stellen im Posting platziert werden…