Die allgemeine Folklore sagt:
Das Typsystem existiert aus einem Grund. Ganzzahlen und Zeiger sind unterschiedliche Typen. Das Umsetzen zwischen ihnen ist in den meisten Fällen ein Fehlverhalten, kann auf einen Entwurfsfehler hinweisen und sollte vermieden werden.
Selbst wenn eine solche Besetzung durchgeführt wird, werden keine Annahmen über die Größe von ganzen Zahlen und Zeiger gemacht werden (Gießen
void*
aufint
die einfachste Art und Weise der Code nicht auf x64 zu machen), und stattint
eines verwenden sollteintptr_t
oderuintptr_t
ausstdint.h
.
Wenn man das weiß, wann ist es tatsächlich nützlich , solche Casts durchzuführen?
(Hinweis: Ein etwas kürzerer Code für den Preis der Portabilität gilt nicht als "tatsächlich nützlich".)
Ein Fall, den ich kenne:
- Einige sperrfreie Multiprozessor-Algorithmen nutzen die Tatsache aus, dass ein 2 + -Byte-zugeordneter Zeiger eine gewisse Redundanz aufweist. Sie verwenden dann beispielsweise die niedrigsten Bits des Zeigers als boolesche Flags. Bei einem Prozessor mit einem geeigneten Befehlssatz kann dies die Notwendigkeit eines Sperrmechanismus beseitigen (was erforderlich wäre, wenn der Zeiger und das Boolesche Flag getrennt wären).
(Hinweis: Diese Vorgehensweise kann in Java sogar sicher über java.util.concurrent.atomic.AtomicMarkableReference durchgeführt werden.)
Noch etwas?
intptr_t
Implementierung ist definiert, sodass ich den sperrfreien Algorithmus auch nicht verwenden würde, wenn ich nicht genau wüsste, auf welchem Compiler er ausgeführt werden würde.uintptr_t
in<stdint.h>
oder<cstdint>
in C ++ 0x. Visual C ++ 2008 ist falsch, wenn Sie es dort haben.Antworten:
Ich gebe manchmal Zeiger auf ganze Zahlen, wenn sie irgendwie Teil eines Hashsums sein müssen. Außerdem habe ich sie in ganze Zahlen umgewandelt, um bei bestimmten Implementierungen ein bisschen mit ihnen herumzuspielen, wobei garantiert ist, dass Zeiger immer ein oder zwei freie Bits übrig haben, wobei ich AVL- oder RB-Bauminformationen in den linken / rechten Zeigern codieren kann, anstatt zusätzliche zu haben Mitglied. Dies ist jedoch alles so implementierungsspezifisch, dass ich empfehle, es niemals als eine gemeinsame Lösung zu betrachten. Ich habe auch gehört, dass mit so etwas manchmal Gefahrenhinweise implementiert werden können.
In einigen Situationen benötige ich eine eindeutige ID pro Objekt, die ich als Anforderungs-ID an z. B. Server weitergebe. Abhängig vom Kontext, in dem ich Speicherplatz sparen muss und es sich lohnt, verwende ich die Adresse meines Objekts als solche ID und muss sie normalerweise in eine Ganzzahl umwandeln.
Bei der Arbeit mit eingebetteten Systemen (wie in Canon-Kameras, siehe chdk) gibt es oft magische Addessen, so dass auch dort oft ein
(void*)0xFFBC5235
oder ähnliches gefunden wirdbearbeiten:
Ich bin gerade gestolpert (in meinen Gedanken) darüber,
pthread_self()
was ein pthread_t zurückgibt, das normalerweise ein typedef für eine vorzeichenlose Ganzzahl ist. Intern ist es jedoch ein Zeiger auf eine Thread-Struktur, die den betreffenden Thread darstellt. Im Allgemeinen kann es an anderer Stelle für einen undurchsichtigen Griff verwendet werden.quelle
unsigned char [sizeof(T *)]
) zu hash ...Dies kann nützlich sein, wenn Sie die Ausrichtung von Typen im Allgemeinen überprüfen, damit falsch ausgerichteter Speicher nicht nur mit SIGBUS / SIGSEGV, sondern mit einer Zusicherung abgefangen wird.
Z.B:
#include <xmmintrin.h> #include <assert.h> #include <stdint.h> int main() { void *ptr = malloc(sizeof(__m128)); assert(!((intptr_t)ptr) % __alignof__(__m128)); return 0; }
(In echtem Code würde ich nicht nur spielen
malloc
, aber es veranschaulicht den Punkt)quelle
Speichern einer doppelt verknüpften Liste mit der Hälfte des Speicherplatzes
Eine verknüpfte XOR-Liste kombiniert die nächsten und vorherigen Zeiger zu einem einzigen Wert derselben Größe. Dazu werden die beiden Zeiger zusammengefügt, wodurch sie wie ganze Zahlen behandelt werden müssen.
quelle
Der nützlichste Fall in meinem Kopf ist der, der tatsächlich das Potenzial hat, Programme viel effizienter zu machen: Eine Reihe von Standard- und allgemeinen Bibliotheksschnittstellen verwenden ein einziges
void *
Argument, das sie an eine Rückruffunktion zurückgeben. Angenommen, Ihr Rückruf benötigt keine große Datenmenge, sondern nur ein einziges ganzzahliges Argument.Wenn der Rückruf erfolgt, bevor die Funktion zurückkehrt, können Sie einfach die Adresse einer lokalen (automatischen)
int
Variablen übergeben, und alles ist in Ordnung. Das beste Beispiel auspthread_create
der Praxis für diese Situation ist jedoch , dass der "Rückruf" parallel ausgeführt wird und Sie nicht garantieren können, dass er das Argument vor derpthread_create
Rückkehr über den Zeiger lesen kann . In dieser Situation haben Sie 3 Möglichkeiten:malloc
eine einzelneint
und lassen Sie den neuen Thread lesen undfree
es.int
und ein Synchronisationsobjekt enthält (z. B. ein Semaphor oder eine Barriere), und lassen Sie den Aufrufer nach dem Aufruf darauf wartenpthread_create
.int
tovoid *
und gib es als Wert weiter.Option 3 ist immens effizienter als jede der anderen Optionen, die beide einen zusätzlichen Synchronisationsschritt beinhalten (für Option 1 ist die Synchronisation in
malloc
/free
und wird mit ziemlicher Sicherheit einige Kosten verursachen, da das Zuweisen und Freigeben von Threads nicht gleich sind). .quelle
union {int i; void* p;}
statt einesvoid*
.union
erforderlichen, wodurch eine hässliche Temperaturvariable erstellt wird. Die POSIX-Echtzeitsignalschnittstellen verwendeten diesen Ansatz (union sigval
) und jeder hasst ihn ...Ein Beispiel ist in Windows, zB die
SendMessage()
undPostMessage()
Funktionen. Sie nehmen aHWnd
(ein Handle für ein Fenster), eine Nachricht (ein integraler Typ) und zwei Parameter für die Nachricht, aWPARAM
und anLPARAM
. Beide Parametertypen sind ganzheitlich, aber manchmal müssen Sie Zeiger übergeben, abhängig von der Nachricht, die Sie senden. Dann müssen Sie einen Zeiger auf einLPARAM
oder setzenWPARAM
.Ich würde es im Allgemeinen wie die Pest vermeiden . Wenn Sie einen Zeiger speichern müssen, verwenden Sie einen Zeigertyp, falls dies möglich ist.
quelle
LPARAM
ist es kein integraler Typ, sondernLONG_PTR
- eine Vereinigung eines Zeigers und eines integralen Typs. Aber es ist in der Tat ein bisschen Hackery. @DeadMG: Sie könnten nebenbeiSendMessage
. Aber das Problem bleibt beiGetMessage
. Sie können das nicht überladen, weil Sie nicht vorhersagen können, welche Nachricht Sie erhalten.In eingebetteten Systemen ist es sehr üblich, auf Hardwaregeräte mit Speicherzuordnung zuzugreifen, bei denen sich die Register an festen Adressen in der Speicherzuordnung befinden. Ich modelliere Hardware in C und C ++ oft anders (mit C ++ können Sie Klassen und Vorlagen nutzen), aber die allgemeine Idee kann für beide verwendet werden.
Ein kurzes Beispiel: Angenommen, Sie haben ein Timer-Peripheriegerät in der Hardware und es hat 2 32-Bit-Register:
ein frei laufendes "Tick Count" -Register, das mit einer festen Rate (z. B. jede Mikrosekunde) abnimmt
ein Steuerregister, mit dem Sie den Timer starten, den Timer stoppen, einen Timer-Interrupt aktivieren können, wenn wir den Zähler auf Null verringern usw.
(Beachten Sie, dass ein Real-Timer-Peripheriegerät normalerweise erheblich komplizierter ist).
Jedes dieser Register ist ein 32-Bit-Wert, und die "Basisadresse" des Timer-Peripheriegeräts ist 0xFFFF.0000. Sie können die Hardware wie folgt modellieren:
// Treat these HW regs as volatile typedef uint32_t volatile hw_reg; // C friendly, hence the typedef typedef struct { hw_reg TimerCount; hw_reg TimerControl; } TIMER; // Cast the integer 0xFFFF0000 as being the base address of a timer peripheral. #define Timer1 ((TIMER *)0xFFFF0000) // Read the current timer tick value. // e.g. read the 32-bit value @ 0xFFFF.0000 uint32_t CurrentTicks = Timer1->TimerCount; // Stop / reset the timer. // e.g. write the value 0 to the 32-bit location @ 0xFFFF.0004 Timer1->TimerControl = 0;
Es gibt 100 Variationen dieses Ansatzes, deren Vor- und Nachteile für immer diskutiert werden können, aber der Punkt hier ist nur, um eine übliche Verwendung des Umwandelns einer Ganzzahl in einen Zeiger zu veranschaulichen. Beachten Sie, dass dieser Code nicht portierbar ist, an ein bestimmtes Gerät gebunden ist, davon ausgeht, dass der Speicherbereich nicht gesperrt ist usw.
quelle
Es ist niemals sinnvoll, solche Casts durchzuführen, es sei denn, Sie kennen das Verhalten Ihrer Kombination aus Compiler und Plattform vollständig und möchten es ausnutzen (Ihr Fragenszenario ist ein solches Beispiel).
Der Grund, warum ich sage, dass es niemals nützlich ist, ist, dass Sie im Allgemeinen weder die Kontrolle über den Compiler haben noch genau wissen, für welche Optimierungen er sich entscheiden kann. Oder anders ausgedrückt: Sie können den generierten Maschinencode nicht genau steuern. Im Allgemeinen können Sie diese Art von Trick also nicht sicher implementieren.
quelle
(uintptr_t) malloc(n) % 4 == 0
wenn n> 2. Das ist nützlich genug, um interessante Dinge damit zu tun, und Ihr Code ist auf Plattformen, auf denen die angenommene Invariante gilt, korrekt und sicher.uintptr_t asUint = (uintptr_t)somePtr;
könnte so einfach 48 Bit schreiben und lassen Sie die anderen 16 Bits beliebige Werte.Das einzige Mal , dass ich warf einen
pointer
auf eininteger
ist , wenn ich einen Zeiger speichern möchten, aber die einzige Speicher i zur Verfügung haben , ist eine ganze Zahl.quelle
Components
haben eineTag
Eigenschaft, die eine ganze Zahl ist. Wenn ich ein Objekt / eine Struktur / einen String / einen Zeiger mit einem verknüpfen möchteComponent
, kann ich dies über dieTag
Eigenschaft tun .WindowRecord
ein 4-Byte-userInfo
Feld, in dem Sie alle gewünschten Informationen speichern konnten und das üblicherweise zum Speichern eines Zeigers auf eine Hilfsstruktur verwendet wurde. In solchen Fällen müssten Sie einen Zeiger auf int oder long (ich weiß nicht mehr, welche) und wieder zurück setzen, um den Compiler glücklich zu machen.Wann ist es richtig, Zeiger in Ints zu speichern? Es ist richtig, wenn Sie es als das behandeln, was es ist: Die Verwendung einer Plattform oder eines compilerspezifischen Verhaltens.
Das Problem besteht nur dann, wenn in Ihrer Anwendung plattform- / compilerspezifischer Code verstreut ist und Sie Ihren Code auf eine andere Plattform portieren müssen, da Sie Annahmen getroffen haben, die nicht mehr zutreffen. Indem Sie diesen Code isolieren und hinter einer Schnittstelle verstecken, die keine Annahmen über die zugrunde liegende Plattform macht, beseitigen Sie das Problem.
Solange Sie die Implementierung dokumentieren, trennen Sie sie hinter einer plattformunabhängigen Schnittstelle mithilfe von Handles oder etwas, das nicht davon abhängt, wie sie hinter den Kulissen funktioniert, und lassen Sie den Code dann nur auf Plattformen / Compilern bedingt kompilieren, auf denen er getestet wurde und funktioniert, dann gibt es keinen Grund für Sie, keine Voodoo-Magie zu verwenden, auf die Sie stoßen. Sie können sogar große Teile der Assemblersprache, proprietäre API-Aufrufe und Kernel-Systemaufrufe einschließen, wenn Sie möchten.
Wenn Ihre "tragbare" Schnittstelle jedoch ganzzahlige Handles verwendet, haben ganze Zahlen die gleiche Größe wie Zeiger auf die Implementierung für eine bestimmte Plattform, und diese Implementierung verwendet intern Zeiger. Warum nicht einfach die Zeiger als ganzzahlige Handles verwenden? Eine einfache Umwandlung in eine Ganzzahl ist in diesem Fall sinnvoll, da Sie die Notwendigkeit einer Handle / Pointer-Nachschlagetabelle ausschließen.
quelle
Möglicherweise müssen Sie an einer festen bekannten Adresse auf den Speicher zugreifen, dann ist Ihre Adresse eine Ganzzahl und Sie müssen sie einem Zeiger zuweisen. Dies ist in eingebetteten Systemen etwas üblich. Umgekehrt müssen Sie möglicherweise eine Speicheradresse drucken und sie daher in eine Ganzzahl umwandeln.
Oh, und vergessen Sie nicht, dass Sie Zeiger NULL zuweisen und vergleichen müssen, was normalerweise eine Zeigerumwandlung von 0L ist
quelle
Ich habe eine Verwendung für so etwas in netzwerkweiten IDs von Objekten. Eine solche ID würde Identifikationen der Maschine (z. B. IP-Adresse), Prozess-ID und die Adresse des Objekts kombinieren. Um über einen Socket gesendet zu werden, muss der Zeigerteil einer solchen ID in eine ausreichend breite Ganzzahl gesetzt werden, damit er den Transport hin und her überlebt. Der Zeigerteil wird nur dann als Zeiger interpretiert (= auf einen Zeiger zurückgesetzt), wenn dies sinnvoll ist (gleiche Maschine, gleicher Prozess), auf anderen Maschinen oder in anderen Prozessen nur zur Unterscheidung verschiedener Objekte dient.
Das, was man braucht, um zu funktionieren, ist die Existenz
uintptr_t
unduint64_t
ein Integer-Typ mit fester Breite. (Funktioniert nur auf Computern mit höchstens 64 Adressen :)quelle
Unter x64 kann on die oberen Bits von Zeigern zum Markieren verwenden (da nur 47 Bits für den tatsächlichen Zeiger verwendet werden). Dies ist ideal für Dinge wie die Generierung von Laufzeitcode (LuaJIT verwendet diese Technik, die laut Kommentaren eine alte Technik ist), um dieses Tagging und die Tag-Überprüfung durchzuführen. Sie benötigen entweder eine Besetzung oder eine
union
, die im Grunde genommen dasselbe sind .Das Umwandeln von Zeigern auf Ganzzahlen kann auch in Speicherverwaltungssystemen sehr hilfreich sein, die das Binning verwenden, dh: Man könnte den Bin / die Seite für eine Adresse leicht über eine Mathematik finden, ein Beispiel aus einem sperrenlosen Allokator, den ich eine Weile geschrieben habe zurück:
inline Page* GetPage(void* pMemory) { return &pPages[((UINT_PTR)pMemory - (UINT_PTR)pReserve) >> nPageShift]; }
quelle
Ich habe solche Systeme verwendet, wenn ich versuche, Byte für Byte durch ein Array zu gehen. Oft geht der Zeiger mehrere Bytes gleichzeitig, was zu Problemen führt, die sehr schwer zu diagnostizieren sind.
Zum Beispiel int Zeiger:
int* my_pointer;
Das Verschieben
my_pointer++
führt zum Vorrücken von 4 Bytes (in einem 32-Bit-Standardsystem). Allerdings in Bewegung((int)my_pointer)++
wird es jedoch um ein Byte vorgerückt.Es ist wirklich der einzige Weg, dies zu erreichen, außer den Zeiger auf ein (char *) zu setzen. (
(char*)my_pointer)++
Zugegeben, das (char *) ist meine übliche Methode, da es sinnvoller ist.
quelle
(char*)
ist die einzige Methode, die garantiert genau definiert ist.Zeigerwerte können auch eine nützliche Entropiequelle für das Setzen eines Zufallszahlengenerators sein:
int* p = new int(); seed(intptr_t(p) ^ *p); delete p;
Die Boost-UUID-Bibliothek verwendet diesen und einige andere Tricks.
quelle
new int()
(übrigens ist eine Initialisierung nicht erforderlich) ein anderer Wert erzeugt wird. Es gibt gut definierte Entropiequellen, wie/dev/random
Es gibt eine alte und gute Tradition, Zeiger auf ein Objekt als typenlosen Griff zu verwenden. Einige Benutzer verwenden es beispielsweise zum Implementieren der Interaktion zwischen zwei C ++ - Einheiten mit einer flachen C-API. In diesem Fall wird der Handle-Typ als einer der Integer-Typen definiert, und jede Methode muss einen Zeiger in eine Integer konvertieren, bevor er an eine andere Methode übertragen werden kann, die ein abstraktes typloses Handle als einen ihrer Parameter erwartet. Außerdem gibt es manchmal keine andere Möglichkeit, eine zirkuläre Abhängigkeit aufzulösen.
quelle
struct epoll_data
für epoll_ctl zum Beispiel.