web-dev-qa-db-de.com

Kann std :: launder verwendet werden, um einen Objektzeiger in seinen umschließenden Arrayzeiger umzuwandeln?

Der aktuelle Standardentwurf (und vermutlich C++ 17) besagt in [basic.compound/4] :

[Anmerkung: Ein Array-Objekt und sein erstes Element können nicht mit einem Zeiger konvertiert werden, obwohl sie dieselbe Adresse haben. - Endnote]

Ein Zeiger auf ein Objekt kann also nicht reinterpret_cast sein, um seinen umschließenden Array-Zeiger zu erhalten.

Jetzt gibt es std::launder, [ptr.launder/1] :

template<class T> [[nodiscard]] constexpr T* launder(T* p) noexcept;

Benötigt: p repräsentiert die Adresse A eines Bytes im Speicher. Ein Objekt X, das sich in seiner Lebensdauer befindet und dessen Typ T ähnelt, befindet sich an der Adresse A. Alle Speicherbytes, die über das Ergebnis erreichbar wären, sind über p erreichbar (siehe unten).

Und die Definition von erreichbar ist in [ptr.launder/3] :

Hinweise: Ein Aufruf dieser Funktion kann in einem Kernkonstantenausdruck immer dann verwendet werden, wenn der Wert seines Arguments in einem Kernkonstantenausdruck verwendet werden kann. Ein Byte Speicher ist über einen Zeigerwert erreichbar, der auf ein Objekt Y zeigt, wenn es sich innerhalb des von Y belegten Speichers befindet, ein Objekt, das mit Y Zeiger-interkonvertierbar ist, oder das unmittelbar einschließende Array-Objekt, wenn Y ein ist Array-Element . Das Programm ist fehlerhaft, wenn T ein Funktionstyp ist oder nichtig ist.

Auf den ersten Blick scheint es, dass std::launder verwendet werden kann, um die oben erwähnte Konvertierung durchzuführen, da ich einen Teil hervorgehoben habe.

Aber. Wenn p auf ein Objekt eines Arrays zeigt, sind die Bytes des Arrays gemäß dieser Definition erreichbar (auch wenn p nicht Zeiger-in-Array-Zeiger konvertierbar ist), genau wie das Ergebnis des Waschvorgangs. Es scheint also, dass die Definition nichts zu diesem Thema aussagt.

Kann std::launder verwendet werden, um einen Objektzeiger in seinen umschließenden Arrayzeiger zu konvertieren?

12
geza

Dies hängt davon ab, ob das umgebende Array-Objekt ein vollständiges Objekt ist, und falls nicht, können Sie über einen Zeiger auf das umgebende Array-Objekt gültig auf weitere Bytes zugreifen (z. B. weil es ein Array-Element selbst ist oder mit einem größeren Objekt ein Zeiger-Interkonvertierbarer ist oder zeigerinterkonvertierbar mit einem Objekt, das ein Arrayelement ist). Die Anforderung "erreichbar" bedeutet, dass Sie launder nicht verwenden können, um einen Zeiger zu erhalten, mit dem Sie auf mehr Bytes zugreifen können, als der Quellzeigerwert zulässt, wenn undefiniertes Verhalten auftritt. Dadurch wird sichergestellt, dass die Möglichkeit, dass unbekannter Code launder aufruft, keinen Einfluss auf die Escape-Analyse des Compilers hat.

Ich denke, einige Beispiele könnten helfen. Jedes Beispiel unten reinterpret_casts ist ein int*, der auf das erste Element eines Arrays von 10 ints in einer int(*)[10] zeigt. Da sie nicht zeigerkonvertierbar sind, ändert reinterpret_cast den Zeigerwert nicht, und Sie erhalten eine int(*)[10] mit dem Wert "Zeiger auf das erste Element von (was auch immer das Array ist)". In jedem Beispiel wird dann versucht, einen Zeiger auf das gesamte Array zu erhalten, indem Sie std::launder für den Besetzungszeiger aufrufen.

int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0])); 

Das ist in Ordnung; Sie können auf alle Elemente von x über den Quellzeiger zugreifen, und das Ergebnis der launder erlaubt Ihnen keinen Zugriff auf andere Elemente.

int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0])); 

Das ist undefiniert. Sie können nur über den Quellzeiger auf Elemente von x2[0] zugreifen, aber das Ergebnis (das ein Zeiger auf x2[0] wäre) hätte Ihnen Zugriff auf x2 [1] erlaubt, das Sie nicht durch die Quelle durchlaufen können.

struct X { int a[10]; } x3, x4[2]; // assume no padding
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK

Das ist in Ordnung. Sie können auch hier nicht über einen Zeiger auf x3.a auf ein Byte zugreifen, auf das Sie noch nicht zugreifen können.

auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0])); 

Dies ist (soll) undefiniert sein. Sie hätten x4[1] vom Ergebnis aus erreichen können, da x4[0].a mit x4[0] zwischenzeigerkonvertierbar ist. Daher kann ein Zeiger auf den ersten Code reinterpret_cast sein, um einen Zeiger auf den letzteren zu geben, der dann für die Zeigerarithmetik verwendet werden kann. Siehe https://wg21.link/LWG2859

struct Y { int a[10]; double y; } x5;
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0])); 

Und das ist wieder undefiniert, weil Sie x5.y vom resultierenden Zeiger (über reinterpret_cast bis Y*) erreichen konnten, aber der Quellzeiger kann nicht verwendet werden, um darauf zuzugreifen.

7
T.C.

Anmerkung: Jeder nicht-schizophrene Compiler wird das wahrscheinlich gerne akzeptieren, da er einen C-Style oder einen Re-Interpret annehmen würde, also probieren Sie es einfach nicht.

Aber meiner Meinung nach ist die Antwort auf Ihre Frage nein. Das hervorgehobene sofort einschließende Array-Objekt, wenn Y ein Array-Element ist liegt in einem Remark - Absatz, nicht im Requires . Das bedeutet, dass, sofern der Requirement-Abschnitt beachtet wird, auch die Anmerkungen gelten. Da ein Array und sein Elementtyp keine ähnlichen Typen sind, ist die Anforderung nicht erfüllt und std::launder kann nicht verwendet werden.

Was folgt, ist eher eine allgemeine (philosophische) Interpretation. Zum Zeitpunkt von K & R C (in den 70er Jahren) sollte C die Assembly-Sprache ersetzen können. Aus diesem Grund lautete die Regel: Der Compiler muss dem Programmierer gehorchen, vorausgesetzt, der Quellcode kann übersetzt werden. Daher war keine strikte Aliasing-Regel und ein Zeiger nicht mehr als eine Adresse mit zusätzlichen Rechenregeln. Dies hat sich in C99 und C++ 03 stark geändert (nicht von C++ 11 +). Programmierer sollen nun C++ als Hochsprache verwenden. Das bedeutet, dass ein Zeiger nur ein Objekt ist, das den Zugriff auf ein anderes Objekt eines bestimmten Typs ermöglicht, und dass ein Array und sein Elementtyp völlig unterschiedliche Typen sind. Speicheradressen sind jetzt etwas mehr als Implementierungsdetails. Der Versuch, einen Zeiger auf ein Array in einen Zeiger auf sein erstes Element zu konvertieren, verstößt dann gegen die Philosophie der Sprache und könnte den Programmierer in einer späteren Version des Compilers beißen. Natürlich akzeptieren Compiler aus Kompatibilitätsgründen den Compiler immer noch, aber wir sollten nicht einmal versuchen, ihn in modernen Programmen zu verwenden.

2
Serge Ballesta