Wann ist eine ganzzahlige <-> Zeigerumwandlung tatsächlich korrekt?

77

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*auf intdie einfachste Art und Weise der Code nicht auf x64 zu machen), und statt inteines verwenden sollte intptr_toder uintptr_taus stdint.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?

Kos
quelle
5
Die Zuordnung zwischen einem Zeiger und einer intptr_tImplementierung 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.
Andreas Brinck
6
Jeder lockfree Algorithmus nutzt mindestens einige implementierungsspezifische Eigenschaften ...
PlasmaHH
3
@PlasmaHH: Guter Punkt. C (und C ++ vor C ++ 11) hat keine Vorstellung von Multithread-Programmen oder gemeinsamem Programmspeicher. Wenn Sie sperrfreie Algorithmen verwenden, verlassen Sie sich bereits auf implementierungsspezifische Eigenschaften. Beachten Sie dies jedoch, da Sie leicht vergessen können, dass die Implementierung hier nicht "normal" ausgeführt werden muss.
Kevin Cathcart
1
Ist eigentlich uintptr_tin <stdint.h>oder <cstdint>in C ++ 0x. Visual C ++ 2008 ist falsch, wenn Sie es dort haben.
Wilhelmtell
Ich mache kein Visual C ++ und das war ein offensichtlicher Fehler von mir, danke! :)
Kos

Antworten:

38

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*)0xFFBC5235oder ähnliches gefunden wird

bearbeiten:

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.

PlasmaHH
quelle
1
Stattdessen Zeiger des Gießens Werte auf ganze Zahlen für Hashing, sollten Sie stattdessen lesen einfach ihre Darstellung (wie unsigned char [sizeof(T *)]) zu hash ...
R .. GitHub STOPP HELFEN ICE
1
Wie auch vom OP hervorgehoben, haben Zeigerwerte oft eine Redudanz, da die unteren Bits 0 sind. Wenn sie weggeschoben und dann mit z. B. 1000000007 multipliziert werden, ergibt sich oft ein überraschend gut verteilter Hash, der für einige von mir ausreicht Anwendungen. Außerdem bin ich kein Fan davon, Bits und Bits blind zu einem Hash zu addieren. Mit ein wenig Überlegung kann ein schneller domänenspezifischer Hash ohne raketenwissenschaftlichen Aufwand gefunden werden.
PlasmaHH
4
+1 Schön zu sehen, dass Sie die Gefahren Ihrer Arbeit verstehen und anderen vorschlagen, dies nicht zu tun :-) Ich bin schockiert, dass dies auf SO positiv bewertet wird und keine Reihe von Bemerkungen zu "Nicht mikrooptimieren" erhalten.
Phkahler
5
Ich liebe es, wenn Leute, die meine Bibliotheken schreiben, für mich mikrooptimieren. Wenn ich Zeit mit den
Mikrooptimierungen
15

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)

Flexo
quelle
13

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.

Craig Gidney
quelle
1
Ich habe das vergessen;) Cooler Hack für speicherkritische eingebettete Lösungen
Kos
Abgesehen von der Tatsache, dass ein solcher Knoten nicht einfach aus der Liste entfernt werden kann, wenn nur ein Zeiger auf den Knoten angegeben wird.
Maxim Egorushkin
1
Ja, Sie müssen im Allgemeinen zwei benachbarte Knoten kennen, um die Liste zu durchlaufen oder zu ändern. Sie tauschen Platz für Bequemlichkeit. Es wird im verlinkten Artikel behandelt.
Craig Gidney
8

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) intVariablen übergeben, und alles ist in Ordnung. Das beste Beispiel aus pthread_createder 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 der pthread_createRückkehr über den Zeiger lesen kann . In dieser Situation haben Sie 3 Möglichkeiten:

  1. malloceine einzelne intund lassen Sie den neuen Thread lesen und freees.
  2. Übergeben Sie einen Zeiger an eine aufruferlokale Struktur, die das intund ein Synchronisationsobjekt enthält (z. B. ein Semaphor oder eine Barriere), und lassen Sie den Aufrufer nach dem Aufruf darauf warten pthread_create.
  3. Wirf das intto void *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/ freeund wird mit ziemlicher Sicherheit einige Kosten verursachen, da das Zuweisen und Freigeben von Threads nicht gleich sind). .

R .. GitHub HÖREN SIE AUF, EIS ZU HELFEN
quelle
2
Und zu denken, dass es durch das Entwerfen dieser Funktionen zu 100% sicher gemacht werden könnte, ist ein union {int i; void* p;}statt eines void*.
Kos
2
Sicherer, aber viel nerviger zu bedienen. Pre-C99 (dh ohne zusammengesetzte Literale), Übergabe einer unionerforderlichen, wodurch eine hässliche Temperaturvariable erstellt wird. Die POSIX-Echtzeitsignalschnittstellen verwendeten diesen Ansatz ( union sigval) und jeder hasst ihn ...
R .. GitHub STOP HELPING ICE
8

Ein Beispiel ist in Windows, zB die SendMessage()und PostMessage()Funktionen. Sie nehmen a HWnd(ein Handle für ein Fenster), eine Nachricht (ein integraler Typ) und zwei Parameter für die Nachricht, a WPARAMund an LPARAM. 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 ein LPARAModer setzen WPARAM.

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.

Rudy Velthuis
quelle
1
Das ist nicht wirklich eine Verwendung davon, das liegt nur daran, dass es sich um Legacy-Code handelt und diese Art von Design üblich war. In einem moderneren System würden Sie einfach mehrere Rückrufe bereitstellen.
Welpe
Ich mache kein WinAPI, also wusste ich nicht, dass die Leute das machen. Wissen Sie, ob LPARAM und WPARAM von WinAPI garantiert groß genug sind, um einen Zeiger zu erfassen?
Kos
Konzeptionell LPARAMist es kein integraler Typ, sondern LONG_PTR- eine Vereinigung eines Zeigers und eines integralen Typs. Aber es ist in der Tat ein bisschen Hackery. @DeadMG: Sie könnten nebenbei SendMessage. Aber das Problem bleibt bei GetMessage. Sie können das nicht überladen, weil Sie nicht vorhersagen können, welche Nachricht Sie erhalten.
MSalters
@MSalters: Heute ist es möglicherweise ein LONG_PTR, vor einigen Jahren war es noch ein integraler Typ (UINT oder DWORD, IIRC). Sie mussten sie immer noch verwenden, um Zeiger zu übergeben. @ DeadMG: Es ist, wenn du wirfst.
Rudy Velthuis
1
@ Kos: Ja, sie sind garantiert groß genug. Andernfalls würde Windows stark dadurch behindert, dass Benutzer keine Nachrichten mit Zeigerwerten senden könnten. Windows verwendet Nachrichten für fast alle GUI-Inhalte.
Rudy Velthuis
6

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.

Dan
quelle
Ja, die Initialisierung von Zeigern aus Konstanten ist ein gutes Beispiel und in Embedded sehr verbreitet. Integer-> Zeiger ist die häufigste der beiden Konvertierungen, würde ich sagen :)
Kos
3

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.

Oliver Charlesworth
quelle
1
Sie können es nicht portabel implementieren, aber auf einer bestimmten Architektur / einem bestimmten Compiler können Sie es sicher implementieren, wenn Sie die Details verstehen.
Phkahler
Sie benötigen keine enzyklopädischen Kenntnisse über die Optimierungen Ihres Compilers. Wenn Sie beweisen möchten, dass Sie Casts korrekt verwenden, müssen Sie nur einige Invarianten kennen. Zum Beispiel bei allen weit verbreiteten Malloc-Implementierungen, (uintptr_t) malloc(n) % 4 == 0wenn 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.
Jason Orendorff
3
Ich denke, C99 garantiert eine Reihe von Dingen wie: Wenn Sie einen Zeiger auf uintptr_t umwandeln und später denselben Zeiger auf uintptr_t umwandeln, sind die resultierenden ganzzahligen Werte dieselben. Das reicht aus, damit solche Casts bei der Berechnung von Hash-Codes nützlich sind. Eine kleine Invariante reicht weit.
Jason Orendorff
1
@ JasonOrendorff: C99 garantiert das nicht. Es garantiert, dass ein Zeiger-> uintptr_t-> Zeiger-Roundtrip einen Zeiger ergibt, der dem Original entspricht, jedoch bei einer konformen Implementierung mit z. B. 48-Bit-Zeigern und einem 64-Bit-uintptr_t, so etwas wieuintptr_t asUint = (uintptr_t)somePtr; könnte so einfach 48 Bit schreiben und lassen Sie die anderen 16 Bits beliebige Werte.
Supercat
2

Das einzige Mal , dass ich warf einen pointerauf ein integerist , wenn ich einen Zeiger speichern möchten, aber die einzige Speicher i zur Verfügung haben , ist eine ganze Zahl.

Ian Boyd
quelle
3
Und warum willst du das tun? In welcher Situation ist es nützlich? Ich würde nur den Speicher ändern, um ein Zeiger zu sein.
R. Martinho Fernandes
Vielleicht so etwas wie wo in den guten alten C-Rückrufsystemen nur eine Leere * verfügbar ist, könnte es Rückrufsysteme geben, die nur eine size_t zur Verfügung haben ...
PlasmaHH
Ist size_t dafür immer groß genug?
Flexo
2
@R. Martinho Fernandes: Wenn es nicht mein Code ist. Alle Componentshaben eine TagEigenschaft, die eine ganze Zahl ist. Wenn ich ein Objekt / eine Struktur / einen String / einen Zeiger mit einem verknüpfen möchte Component, kann ich dies über die TagEigenschaft tun .
Ian Boyd
3
Beispiel: In "klassischem" MacOS hatten viele Strukturen beispielsweise WindowRecordein 4-Byte- userInfoFeld, 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.
Caleb
2

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.

James O'Doherty
quelle
1

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

deStrangis
quelle
Also gut. Wenn Sie die Bibliotheksroutine schreiben, die Zeiger druckt. Duh!
DeStrangis
In C ++ 0 ist das Nullzeiger-Literal und es ist keine Umwandlung beteiligt. Tatsächlich muss das Bitmuster eines Nullzeigers nicht einmal das gleiche sein wie das einer Ganzzahl derselben Größe mit einem Wert von 0 ...
PlasmaHH
Ja, guter Punkt, aber das ist bei C nicht der Fall (beachten Sie, dass ich das Wort normalerweise verwendet habe). Wenn Sie mit festen konstanten Speicheradressen befasst sind - die Situation, die mir einfällt, wenn Sie eine Ganzzahl für Zeiger-Casts benötigen -, würde ich sagen, dass Sie eher C als C ++ verwenden.
DeStrangis
Und Casts werden in C ++ ohnehin viel weniger verwendet als in C, wo sie unerlässlich sind.
DeStrangis
1

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_tund uint64_tein Integer-Typ mit fester Breite. (Funktioniert nur auf Computern mit höchstens 64 Adressen :)

Jens Gustedt
quelle
1

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];
}
Necrolis
quelle
1
Heh. Mike Pall hat diese Technik nicht erfunden. Ich bin zuversichtlich, dass es auf frühe Lisp-Implementierungen zurückgeht.
Jason Orendorff
4
AMD warnt ausdrücklich davor, da es schrecklich kaputt geht, wenn der Adressraum erweitert wird. Genau wie beim 68000, als der Adressraum von 24 auf 32 Bit erweitert wurde.
Bo Persson
Nur um Jason zu folgen, diese Technik ist wirklich uralt und wurde in unzähligen Sprachlaufzeiten verwendet.
Stephen Canon
@ Bo: Hast du einen Link dafür? Neugierig, was es sonst noch enthalten könnte. Jason: aktualisiert, um Ihren Kommentar
wiederzugeben
1
@Eonil: natürlich , wenn Sie Speicher-Management - Systeme tun Zeiger Tagging oder machen zu wollen, müßten Sie Ihre zugrunde liegende Architektur wissen, vor allem meiner Antwort auf x86 konzentriert und unter x86_64, alle Adressraum ist linear, so es ist garantiert :)
Necrolis
0

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.

Richard
quelle
(char*)ist die einzige Methode, die garantiert genau definiert ist.
GManNickG
0

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.

Invers
quelle
Es kann nicht garantiert werden, dass in nachfolgenden Läufen new int()(übrigens ist eine Initialisierung nicht erforderlich) ein anderer Wert erzeugt wird. Es gibt gut definierte Entropiequellen, wie/dev/random
Maxim Egorushkin
0

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.

Sergey Shamov
quelle
Ich kann mir eine solche Situation nicht vorstellen ... Könnten Sie möglicherweise ein Codebeispiel bereitstellen?
Kos
Es ist nicht vorstellbar, weil es keine abstrakte Situation ist. Es ist ein sehr konkreter Fall, der mit einem kurzen Beispiel nur schwer zu veranschaulichen ist. Die allgemeine Regel lautet: Wenn Sie eine Interaktion ohne typenlose Handles implementieren können, verwenden Sie sie nicht. Aber eines Tages könnte man sich der Tatsache stellen, dass es keinen anderen Weg gibt. Verwenden Sie es in diesem Fall ohne Zweifel. Es ist legal, wenn Sie zur Laufzeit einen Objekttyp überprüfen, nachdem Sie Zeiger aus einer Ganzzahl entfernt haben (z. B. mit der Methode get_type_id ()).
Sergey Shamov
Sie verwenden oft eine Vereinigung eines Zeigers und einer ganzen Zahl. Siehe struct epoll_datafür epoll_ctl zum Beispiel.
Maxim Egorushkin
Die Gewerkschaften sind in einer solchen Situation nützlich. Es ist wie bei einem Casting, aber eine Reihe von Zieltypen ist begrenzt.
Sergey Shamov