Was ist der Datentyp uintptr_t?

Antworten:

199

uintptr_tist ein vorzeichenloser Integer-Typ, der einen Datenzeiger speichern kann. Dies bedeutet normalerweise, dass es die gleiche Größe wie ein Zeiger hat.

Es ist optional in C ++ 11 und späteren Standards definiert.

Ein häufiger Grund für den Wunsch nach einem Integer-Typ, der den Zeigertyp einer Architektur enthalten kann, besteht darin, ganzzahlspezifische Operationen an einem Zeiger auszuführen oder den Typ eines Zeigers zu verdecken, indem er als ganzzahliges "Handle" bereitgestellt wird.

Bearbeiten: Beachten Sie, dass Steve Jessop einige sehr interessante zusätzliche Details (die ich nicht stehlen werde) in einer anderen Antwort hier für Sie pedantischen Typen hat :)

Drew Dormann
quelle
53
Beachten Sie, dass dies size_tnur ausreichen muss, um die Größe des größten Objekts zu halten, und kleiner als ein Zeiger sein kann. Dies wäre auf segmentierten Architekturen wie dem 8086 (16 Bit size_t, aber 32 Bit void*) zu erwarten
MSalters
3
Um den Unterschied zwischen zwei Zeigern darzustellen, haben Sie ptrdiff_t. uintptr_tist nicht dafür gedacht.
Jalf
3
@jalf: Für den Unterschied, ja, aber für die Entfernung möchten Sie einen vorzeichenlosen Typ.
Drew Dormann
Die Definition von uintptr_t ist anscheinend auch in C ++ 11 nicht obligatorisch (also kein Standard)! cplusplus.com/reference/cstdint (Ich habe den Hinweis von Steve Jessop erhalten)
Antonio
2
@MarcusJ ist unsigned intnormalerweise nicht groß genug. Aber es könnte groß genug sein. Dieser Typ existiert speziell, um alle "Annahmen" zu entfernen .
Drew Dormann
239

Als die Frage gestellt wurde, uintptr_twar das erste, was nicht in C ++ war. Es ist in C99, in <stdint.h>, als optionaler Typ. Viele C ++ 03-Compiler stellen diese Datei zur Verfügung. Es ist auch in C ++ 11, <cstdint>wo es wiederum optional ist und sich für die Definition auf C99 bezieht.

In C99 ist es definiert als "ein vorzeichenloser Integer-Typ mit der Eigenschaft, dass jeder gültige Zeiger auf void in diesen Typ konvertiert und dann wieder in einen Zeiger auf void konvertiert werden kann und das Ergebnis dem ursprünglichen Zeiger entspricht".

Nehmen Sie das als das, was es sagt. Es sagt nichts über die Größe aus.

uintptr_tkönnte die gleiche Größe haben wie a void*. Es könnte größer sein. Es könnte möglicherweise kleiner sein, obwohl sich eine solche C ++ - Implementierung pervers nähert. Auf einer hypothetischen Plattform mit void*32 Bit, aber nur 24 Bit virtuellem Adressraum können Sie beispielsweise ein 24-Bit-Format verwenden, uintptr_tdas die Anforderungen erfüllt. Ich weiß nicht, warum eine Implementierung das tun würde, aber der Standard erlaubt es.

Steve Jessop
quelle
8
Danke für das "<stdint.h>". Meine Anwendung wurde aufgrund der Deklaration von uintptr_t nicht kompiliert. Aber wenn ich Ihren Kommentar lese, füge ich "#include <stdint.h>" hinzu und ja, jetzt funktioniert es. Vielen Dank!
JavaRunner
2
Zum Beispiel, um ausgerichteten Speicher zuzuweisen , unter anderem?
Legends2k
4
Ein weiterer häufiger Anwendungsfall beim Umwandeln eines Zeigers auf ein int ist das Erstellen eines undurchsichtigen Handles, das den Zeiger verbirgt. Dies ist nützlich, um einen Verweis auf ein Objekt von APIs zurückzugeben, bei denen Sie das Objekt für die Bibliothek privat halten und verhindern möchten, dass Anwendungen darauf zugreifen können. Die Anwendung ist dann gezwungen, die API zu verwenden, um alle Operationen an dem Objekt auszuführen
Joel Cunningham
3
@ JoelCunningham: Das funktioniert, unterscheidet sich aber nicht wirklich von der Verwendung void*. Dies wirkt sich jedoch auf mögliche zukünftige Richtungen aus, insbesondere wenn Sie Änderungen vornehmen möchten, um etwas zu verwenden, das eigentlich nur ein ganzzahliges Handle und überhaupt kein konvertierter Zeiger ist.
Steve Jessop
2
@CiroSantilli 事件 事件 2016 六四 事件 法轮功 Ein häufiger Anwendungsfall besteht darin, nur ein int an eine API zu übergeben, die eine Leere * für generische Daten erwartet. Spart Ihnen die Tastenanschläge typedef struct { int whyAmIDoingThis; } SeriouslyTooLong; SeriouslyTooLong whyAmNotDoneYet; whyAmINotDoneYet.whyAmIDoingThis = val; callback.dataPtr = &whyAmINotDoneYet;. Stattdessen : callback.dataPtr = (void*)val. Auf der anderen Seite bekommt man es natürlich void*und muss es zurückwerfen int.
Francesco Dondi
19

Es ist ein vorzeichenloser Integer-Typ, der genau die Größe eines Zeigers hat. Wann immer Sie mit einem Zeiger etwas Ungewöhnliches tun müssen - wie zum Beispiel alle Bits invertieren (fragen Sie nicht warum), wandeln Sie uintptr_tihn in eine gewöhnliche Ganzzahl um und manipulieren ihn dann zurück.

scharfer Zahn
quelle
6
Natürlich könnten Sie das tun, aber das wäre natürlich undefiniertes Verhalten. Ich glaube, dann können Sie mit dem Ergebnis einer Besetzung von uintptr_t nur noch unverändert weitergeben und zurückgeben - alles andere ist UB.
Sleske
Es gibt Zeiten, in denen Sie mit den Bits spielen müssen, und es würde normalerweise Compilerfehler erzeugen. Ein häufiges Beispiel ist das Erzwingen eines 16-Byte-ausgerichteten Speichers für bestimmte Video- und leistungskritische Anwendungen. stackoverflow.com/questions/227897/…
Cloud
3
@sleske das stimmt nicht. Auf Maschinen mit selbstausgerichteten Typen sind die beiden niedrigstwertigen Bits eines Zeigers Null (da Adressen Vielfache von 4 oder 8 sind). Ich habe Programme gesehen, die dies ausnutzen, um Daten zu komprimieren.
Saadtaame
3
@saadtaame: Ich habe gerade darauf hingewiesen, dass dies UB nach dem C-Standard ist . Das bedeutet nicht, dass es auf einigen Systemen nicht definiert werden kann - Compiler und Laufzeiten können ein bestimmtes Verhalten für etwas definieren, das in Standard C UB ist. Es gibt also keinen Widerspruch :-).
Sleske
2
Es ist nicht unbedingt genau die Größe eines Zeigers. Alle Standardgarantien bestehen darin, dass das Konvertieren eines void*Zeigerwerts in uintptr_tund wieder zurück einen void*Wert ergibt , der dem ursprünglichen Zeiger entspricht. uintptr_that normalerweise die gleiche Größe wie void*, aber das ist weder garantiert, noch gibt es eine Garantie dafür, dass die Bits des konvertierten Werts eine bestimmte Bedeutung haben. Und es gibt keine Garantie dafür, dass ein konvertierter Zeiger-zu-Funktion-Wert ohne Informationsverlust gespeichert werden kann. Schließlich ist es nicht garantiert zu existieren.
Keith Thompson
15

Es gibt bereits viele gute Antworten auf den Teil "Was ist der Datentyp uintptr_t?". Ich werde versuchen, das "Wofür kann es verwendet werden?" Teil in diesem Beitrag.

Hauptsächlich für bitweise Operationen an Zeigern. Denken Sie daran, dass in C ++ keine bitweisen Operationen an Zeigern ausgeführt werden können. Aus Gründen siehe Warum können Sie in C keine bitweisen Operationen am Zeiger ausführen, und gibt es einen Weg, dies zu umgehen?

Um bitweise Operationen an Zeigern durchzuführen, müsste man also Zeiger umwandeln, um unitpr_t einzugeben, und dann bitweise Operationen ausführen.

Hier ist ein Beispiel für eine Funktion, die ich gerade geschrieben habe, um bitweise exklusiv zu arbeiten, oder für 2 Zeiger, die in einer verknüpften XOR-Liste gespeichert werden sollen, damit wir wie eine doppelt verknüpfte Liste in beide Richtungen gehen können, ohne die Strafe, 2 Zeiger in jedem Knoten zu speichern .

 template <typename T>
 T* xor_ptrs(T* t1, T* t2)
 {
     return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(t1)^reinterpret_cast<uintptr_t>(t2));
  }
BGR
quelle
1
Abgesehen von der Bitarithmetik ist es auch schön, wenn Sie eine Semantik basierend auf Adressen anstelle von Objektzählungen wünschen.
Alex
4

Da ich das Risiko habe, ein weiteres Nekromanten-Abzeichen zu erhalten, möchte ich eine sehr gute Verwendung für uintptr_t (oder sogar intptr_t) hinzufügen, bei der testbarer eingebetteter Code geschrieben wird. Ich schreibe hauptsächlich eingebetteten Code für verschiedene Arm- und derzeit Tensilica-Prozessoren. Diese haben verschiedene native Busbreiten und die Tensilica ist tatsächlich eine Harvard-Architektur mit separaten Code- und Datenbussen, die unterschiedliche Breiten haben können. Ich verwende einen testgetriebenen Entwicklungsstil für einen Großteil meines Codes, was bedeutet, dass ich Unit-Tests für alle Code-Einheiten durchführe, die ich schreibe. Unit-Tests auf der tatsächlichen Zielhardware sind mühsam, daher schreibe ich normalerweise alles auf einem Intel-basierten PC, entweder unter Windows oder Linux, mit Ceedling und GCC. Abgesehen davon beinhaltet viel eingebetteter Code Bit-Twiddling und Adressmanipulationen. Die meisten meiner Intel-Maschinen sind 64-Bit. Wenn Sie also den Adressmanipulationscode testen möchten, benötigen Sie ein verallgemeinertes Objekt, mit dem Sie rechnen können. Mit uintptr_t können Sie Ihren Code daher unabhängig vom Computer debuggen, bevor Sie versuchen, ihn auf der Zielhardware bereitzustellen. Ein weiteres Problem besteht darin, dass für einige Maschinen oder sogar Speichermodelle auf einigen Compilern Funktionszeiger und Datenzeiger unterschiedliche Breiten haben. Auf diesen Computern erlaubt der Compiler möglicherweise nicht einmal das Umwandeln zwischen den beiden Klassen, aber uintptr_t sollte auch in der Lage sein, zu halten.

bd2357
quelle
Nicht sicher, ob relevant ... Haben Sie "undurchsichtige Typedefs" ausprobiert? Vide: youtube.com/watch?v=jLdSjh8oqmE
Shycha
@sleske Ich wünschte, das wäre in C verfügbar. Aber stdint.h zu haben ist besser als nichts. (Wünschen Sie sich auch Aufzählungen, in denen die Eingabe stärker ist, aber die meisten Debugger leisten gute Arbeit, um sie auf irgendeine Weise herauszufinden.)
bd2357