2014-03-25 2 views
5

Nun wissen wir, dass die Out-of-Bounds-Pointer-Arithmetik ein undefiniertes Verhalten hat, wie in dieser SO question beschrieben.Kann std :: uintptr_t verwendet werden, um ein undefiniertes Verhalten der Out-of-Bounds-Zeigerarithmetik zu vermeiden?

Meine Frage ist: können wir umgehen solche Beschränkung durch Umwandlung in std :: uintptr_t für arithmetische Operationen und dann zurück in den Zeiger? Funktioniert das garantiert?

Zum Beispiel:

char a[5]; 
auto u = reinterpret_cast<std::uintptr_t>(a) - 1; 
auto p = reinterpret_cast<char*>(u + 1); // OK? 

Die reale Welt Nutzung ist für offsetted Speicherzugriff zu optimieren - statt p[n + offset], ich will offset_p[n] tun.

EDIT Um die Frage noch deutlicher zu machen:

ein Basiszeiger p eines char-Arrays, wenn p + n ein gültiger Zeiger ist, wird reinterpret_cast<char*>(reinterpret_cast<std::uintptr_t>(p) + n) die gleichen gültigen Zeiger erhalten gewährleistet werden?

Antwort

4

Ja, das ist legal, aber man muss reinterpret_cast genau der gleiche uintptr_t Wert wieder auf char*.

(daher, was es du bist Absicht ist illegal zu tun,. Das heißt, auf einen Zeiger einen verschiedenen Wert Umwandlung zurück)

5.2.10 umdeuten werfen

4. Ein Zeiger kann explizit in einen beliebigen integralen Typ konvertiert werden, der groß genug ist, um ihn aufzunehmen. Die Mapping-Funktion ist implementierungsdefiniert.

5. Ein Wert vom Integraltyp oder Aufzählungstyp kann explizit in einen Zeiger konvertiert werden. Ein Zeiger, der in eine Ganzzahl mit ausreichender Größe konvertiert wurde (falls eine solche in der Implementierung vorhanden ist) und zurück zum selben Zeigertyp wird seinen ursprünglichen Wert haben;

(Beachten Sie, dass es keinen Weg würde in der Regel für den Compiler zu wissen, dass Sie eine abgezogen und dann hinzugefügt, um es zurück.)

+0

Also 'reinterpret_cast (offset_p-offset + n_less_equal_array_size)' ist immer gut ertragen, wo offset_p den versetzten ptr-Wert in std :: uintptr_t speichert? – Jamboree

+0

Nicht sicher, ob ich folge. Aber die allgemeine Antwort ist, wenn die ganze Zahl, die du an "reinterpret_cast" übergibst, der gleiche Wert ist wie der, den du daraus erhalten hast, dann ist es legal. Wenn nicht, dann ist es illegal. –

+0

Für das erste Element von Array ist es derselbe Wert, aber ich möchte wissen, ob es auch für andere Elemente im Array funktioniert. – Jamboree

11

Nein, uintptr_t nicht Bedeutung verwendet werden können, zu vermeiden, undefiniertes Verhalten bei der Zeigerarithmetik.

Zum einen, zumindest in C gibt es keine Garantie, dass uintptr_t überhaupt existiert. Voraussetzung ist, dass ein beliebiger Wert vom Typ void* in uintptr_t und zurück konvertiert werden kann, wodurch der ursprüngliche Wert ohne Informationsverlust erreicht wird. Im Prinzip ist möglicherweise kein vorzeichenloser Integertyp vorhanden, der breit genug ist, um alle Zeigerwerte zu enthalten. (Ich nehme an, das gleiche gilt für C++, da C++ den größten Teil der C-Standardbibliothek erbt und durch Verweis auf den C-Standard definiert.)

Auch wenn uintptr_t existiert, gibt es keine Garantie, dass eine gegebene arithmetische Operation auf a Wert tut das gleiche wie die entsprechende Operation auf einen Zeigerwert.

Zum Beispiel habe ich an Systemen (Cray Vektorsysteme, T90 und SV1) gearbeitet, auf denen Bytezeiger in Software implementiert sind. Eine native Adresse ist eine 64-Bit-Adresse, die sich auf ein 64-Bit-Wort bezieht. Es gibt keine Hardwareunterstützung für die Byteadressierung. Ein Zeiger char* oder void* besteht aus einem Wortzeiger mit einem 3-Bit-Offset, der in den ansonsten nicht verwendeten höherwertigen Bits gespeichert ist. Die Konvertierung zwischen Ganzzahlen und Zeigern kopiert einfach die Bits. Wenn man also ein char* inkrementiert, würde es so weit fortschreiten, dass es auf das nächste 8-Bit-Byte im Speicher zeigt; Inkrementieren eines uintptr_t, erhalten durch Umwandeln eines char*, würde es voranbringen, um auf das nächste 64-Bit-Wort zu zeigen.

Das ist nur ein Beispiel. Ganz allgemein sind Konvertierungen zwischen Zeigern und Ganzzahlen implementationsdefiniert, und der Sprachstandard gibt keine Garantie für die Semantik dieser Konvertierungen (außer in einigen Fällen, die zurück in einen Zeiger konvertiert werden).

Also ja, Sie können einen Zeigerwert in uintptr_t konvertieren (wenn dieser Typ vorhanden ist) und arithmetische Berechnungen durchführen, ohne ein undefiniertes Verhalten zu riskieren - aber das Ergebnis kann bedeutungsvoll sein oder auch nicht.

Es passiert, dass auf den meisten Systemen, die Zuordnung zwischen Zeigern und Ganzzahlen ist einfacher, und Sie können wahrscheinlich mit dieser Art von Spiel kommen . Aber Sie sollten lieber direkt mit Zeigerarithmetik arbeiten und nur sehr vorsichtig sein, um ungültige Operationen zu vermeiden.

+0

+1 für das * tatsächliche Beispiel * von wo es brechen würde. – Mehrdad