Was genau ist ein IntPtr?

170

Durch die Verwendung von IntelliSense und das Betrachten des Codes anderer Personen bin ich auf diesen IntPtrTyp gestoßen. Jedes Mal, wenn es verwendet werden musste, habe ich einfach nulloder IntPtr.Zeround die meisten Funktionen zum Funktionieren gebracht. Was genau ist es und wann / warum wird es verwendet?

Callum Rogers
quelle

Antworten:

158

Es ist eine "native (plattformspezifische) Ganzzahl". Es wird intern void*als Ganzzahl dargestellt, aber als Ganzzahl angezeigt. Sie können es immer dann verwenden, wenn Sie einen nicht verwalteten Zeiger speichern müssen und keinen unsafeCode verwenden möchten . IntPtr.Zeroist effektiv NULL(ein Nullzeiger).

Sam Harwell
quelle
53
Ein Zeiger zeigt auf eine Adresse im Speicher. In verwalteten Sprachen haben Sie Referenzen (die Adresse kann sich verschieben), während Sie in nicht verwalteten Sprachen Zeiger haben (die Adresse ist fest)
Colin Mackay
93
Im Allgemeinen (programmierübergreifend) ist ein Zeiger eine Zahl, die einen physischen Speicherort darstellt. Ein Nullzeiger ist (fast immer) einer, der auf 0 zeigt, und wird allgemein als "auf nichts zeigen" anerkannt. Da Systeme unterschiedliche Mengen an unterstütztem Speicher haben, ist nicht immer die gleiche Anzahl von Bytes erforderlich, um diese Anzahl zu speichern. Daher nennen wir eine Ganzzahl mit "nativer Größe", die einen Zeiger auf einem bestimmten System enthalten kann.
Sam Harwell
5
+1 für diesen Kommentar @ 280Z28, das ist die prägnanteste Erklärung für Zeiger, die ich je gesehen habe.
BlueRaja - Danny Pflughoeft
9
Es heißt IntPtr, da Sie den analogen Typ intptr_t verwenden müssen, um es aus nicht verwaltetem nativem Code, C / C ++, zu verwenden. IntPtr von C # entspricht genau intptr_t von C / C ++. Und es ist wahrscheinlich als intptr_t implementiert. In C / C ++ hat der Typ intptr_t garantiert dieselbe Größe wie der Typ void *.
1етър Петров
2
@Trap "Ein plattformspezifischer Typ, der zur Darstellung eines Zeigers oder eines Handles verwendet wird." Keine "plattformspezifische Ganzzahl", keine "plattformspezifische Art, einen Zeiger oder ein Handle darzustellen". IntPtrmacht vollkommen Sinn, weil es eine ganze Zahl (wie in der mathematischen ganzen Zahl) und ein Zeiger (wie im Zeiger) ist. Der IntTeil hat wenig mit dem Typ int zu tun - es ist ein Konzept, kein bestimmter Typ. Um fair zu sein, sagt die CLI-Spezifikation nur, dass es sich tatsächlich um eine "ganzzahlige, native Größe" handelt. Na
ja
69

Es ist ein Werttyp, der groß genug ist, um eine Speicheradresse zu speichern, wie sie in nativem oder unsicherem Code verwendet wird, aber nicht direkt als Speicheradresse in sicher verwaltetem Code verwendet werden kann.

Sie können verwenden IntPtr.Size, um herauszufinden, ob Sie in einem 32-Bit- oder 64-Bit-Prozess ausgeführt werden, da dieser 4 bzw. 8 Byte beträgt.

Daniel Earwicker
quelle
16
Guter Punkt zum Erkennen der Größe des Adressraums für den Prozess.
Noldorin
@Noldorin Stimmt, aber nicht unbedingt zuverlässig. In der Vergangenheit gab es viele Architekturen mit mehreren Zeigertypen, und unter Windows werden IntPtrauch Handles dargestellt, die unabhängig von der Architektur 32-Bit sind ( Sizein diesem Fall jedoch immer noch 8). Die CLI-Spezifikation stellt nur fest, dass es sich um eine Ganzzahl handelt, die "native Größe" hat, aber nicht wirklich viel aussagt.
Luaan
@Luaan, das ändert nichts an meiner Antwort, oder? IntPtr wird als Wert bezeichnet (und wird in der gesamten CLR-Quelle verwendet), der groß genug ist, um eine Speicheradresse aufzunehmen. Es kann natürlich kleinere Werte enthalten. Einige Architekturen haben mehrere Zeigertypen, müssen jedoch einen haben, der der größte der Menge ist.
Daniel Earwicker
@ DanielEarwicker Soweit mir bekannt ist, ist dies bei keiner aktuellen .NET-Implementierung ein Problem. Das (historische) Problem betrifft jedoch nicht nur die Größe - die verschiedenen Zeiger sind möglicherweise vollständig inkompatibel. In einem Beispiel, das näher an heute liegt, würde PAE 64-Bit-Adressen verwenden, obwohl die "native Zeigergröße" noch 32-Bit war. Es geht bis zum Argument zurück "Was bedeutet" 32-Bit-System "wirklich?" Wir haben jetzt numerische 256-Bit- und 512-Bit-Register, nennen sie aber immer noch 64-Bit. Auch wenn Sie mit Ihren 64-Bit- "Zeigern" 64-Bit-Speicher nicht adressieren können . Es ist ein Chaos.
Luaan
1
Einige davon sind kompliziert, aber IntPtr ist immer noch "ein Werttyp, der groß genug ist, um eine Speicheradresse zu speichern". Es ist nicht so groß wie das größte Hardwareregister, um eines Ihrer Beispiele zu nennen. Dafür ist es einfach nicht da. Es ist groß genug, um eine Speicheradresse darzustellen. Dann gibt es solche Sachen: stackoverflow.com/questions/12006854/… wo wir das Wort "Zeiger" haben, das für etwas anderes als eine Speicheradresse verwendet wird - weshalb ich speziell "Speicheradresse" sagte.
Daniel Earwicker
48

Hier ist ein Beispiel:

Ich schreibe ein C # -Programm, das mit einer Hochgeschwindigkeitskamera verbunden ist. Die Kamera verfügt über einen eigenen Treiber, der Bilder erfasst und für mich automatisch in den Arbeitsspeicher des Computers lädt.

Wenn ich bereit bin, das neueste Bild in mein Programm zu integrieren, um damit zu arbeiten, stellt mir der Kameratreiber eine IntPtr zur Verfügung, mit der das Bild BEREITS im physischen Speicher gespeichert wird, sodass ich keine Zeit / Ressourcen für die Erstellung eines anderen verschwenden muss Speicherblock zum Speichern eines Bildes, das sich bereits im Speicher befindet. Der IntPtr zeigt mir nur, wo sich das Bild bereits befindet.

bufferz
quelle
7
Mit IntPtr simple können Sie also einen nicht verwalteten Zeiger (wie den in Ihrem Kameratreiber verwendeten) in verwaltetem Code verwenden?
Callum Rogers
5
Ja. In diesem Fall verwendet der Kameratreiber höchstwahrscheinlich nicht verwaltete Treiber unter der Haube, aber um in der Nur-Verwalteten-Welt ordnungsgemäß zu funktionieren, bietet er den IntPtr, mit dem ich sicher mit den Daten arbeiten kann.
Bufferz
3
Warum gibt es Ihnen dann nicht einfach einen Stream (Byte-Array) zurück? Warum ist es unsicher oder kann den Wert nicht zurückgeben?
Der Muffin-Mann
35

Eine direkte Interpretation

Ein IntPtr ist eine Ganzzahl , die dieselbe Größe wie ein Zeiger hat .

Mit IntPtr können Sie einen Zeigerwert in einem Nicht-Zeigertyp speichern. Diese Funktion ist in .NET wichtig, da die Verwendung von Zeigern sehr fehleranfällig und daher in den meisten Kontexten unzulässig ist. Indem der Zeigerwert in einem "sicheren" Datentyp gespeichert werden kann, kann die Installation zwischen unsicheren Codesegmenten in sichererem Code auf hoher Ebene implementiert werden - oder sogar in einer .NET-Sprache, die Zeiger nicht direkt unterstützt.

Die Größe von IntPtr ist plattformspezifisch, dieses Detail muss jedoch selten berücksichtigt werden, da das System automatisch die richtige Größe verwendet.

Der Name "IntPtr" ist verwirrend - so etwas Handlehätte besser passen können . Meine anfängliche Vermutung war, dass "IntPtr" ein Zeiger auf eine ganze Zahl war. Die MSDN-Dokumentation von IntPtr geht auf etwas kryptische Details ein, ohne jemals viel Einblick in die Bedeutung des Namens zu geben.

Eine alternative Perspektive

An IntPtrist ein Zeiger mit zwei Einschränkungen:

  1. Es kann nicht direkt dereferenziert werden
  2. Es kennt nicht den Typ der Daten, auf die es verweist.

Mit anderen Worten, a IntPtrist wie a void*- aber mit der zusätzlichen Funktion, dass es für die grundlegende Zeigerarithmetik verwendet werden kann (aber nicht sollte).

Um eine Dereferenzierung durchzuführen IntPtr, können Sie sie entweder in einen echten Zeiger umwandeln (eine Operation, die nur in "unsicheren" Kontexten ausgeführt werden kann) oder an eine Hilfsroutine übergeben, wie sie von der InteropServices.MarshalKlasse bereitgestellt wird . Die Verwendung der MarshalKlasse vermittelt die Illusion von Sicherheit, da Sie sich nicht in einem expliziten "unsicheren" Kontext befinden müssen. Es beseitigt jedoch nicht das Absturzrisiko, das mit der Verwendung von Zeigern verbunden ist.

Brent Bradburn
quelle
2
Es gibt einige Präzedenzfälle für den Namen "intptr" aus dem C99-Standard der Programmiersprache "C". linux.die.net/man/3/intptr_t .
Brent Bradburn
17

Was ist ein Zeiger?

In allen Sprachen ist ein Zeiger ein Variablentyp, in dem eine Speicheradresse gespeichert ist. Sie können ihn entweder bitten, Ihnen die Adresse mitzuteilen, auf die er zeigt, oder den Wert an der Adresse , auf die er zeigt.

Ein Zeiger kann als eine Art Lesezeichen betrachtet werden. Anstatt schnell zu einer Seite in einem Buch zu springen, wird ein Zeiger verwendet, um Speicherblöcke zu verfolgen oder zuzuordnen.

Stellen Sie sich den Speicher Ihres Programms genau wie ein großes Array von 65535 Bytes vor.

Zeiger zeigen gehorsam

Zeiger merken sich jeweils eine Speicheradresse und zeigen daher jeweils auf eine einzelne Adresse im Speicher.

Als Gruppe merken sich Zeiger Speicheradressen und rufen sie ab, wobei jeder Befehl ad nauseum befolgt wird.

Du bist ihr König.

Zeiger in C #

Insbesondere in C # ist ein Zeiger eine ganzzahlige Variable, die eine Speicheradresse zwischen 0 und 65534 speichert.

Zeiger sind ebenfalls spezifisch für C # und vom Typ int und daher signiert.

Sie können jedoch keine negativ nummerierten Adressen verwenden und auch nicht auf eine Adresse über 65534 zugreifen. Bei jedem Versuch wird eine System.AccessViolationException ausgelöst.

Ein Zeiger namens MyPointer wird wie folgt deklariert:

int * MyPointer;

Ein Zeiger in C # ist ein int, aber die Speicheradressen in C # beginnen bei 0 und reichen bis zu 65534.

Spitze Dinge sollten mit besonderer Sorgfalt behandelt werden

Das Wort unsicher ist beabsichtigt , Sie zu erschrecken, und für einen sehr guten Grund: Pointers spitze Dinge sind, und spitze Dinge wie Schwerter, Äxte, Zeiger usw. sollte mit besonderer Sorgfalt behandelt werden.

Zeiger geben dem Programmierer eine strenge Kontrolle über ein System. Daher haben Fehler wahrscheinlich schwerwiegendere Konsequenzen.

Um Zeiger verwenden zu können, muss unsicherer Code in den Eigenschaften Ihres Programms aktiviert sein, und Zeiger müssen ausschließlich in Methoden oder Blöcken verwendet werden, die als unsicher markiert sind.

Beispiel eines unsicheren Blocks

unsafe
{
    // Place code carefully and responsibly here.

}

Verwendung von Zeigern

Wenn Variablen oder Objekte deklariert oder instanziiert werden, werden sie im Speicher gespeichert.

  • Deklarieren Sie einen Zeiger mit dem Symbolpräfix *.

int *MyPointer;

  • Um die Adresse einer Variablen abzurufen, verwenden Sie das Präfix & symbol.

MyPointer = &MyVariable;

Sobald einem Zeiger eine Adresse zugewiesen wurde, gilt Folgendes:

  • Ohne * Präfix, um auf die Speicheradresse zu verweisen, auf die als int verwiesen wird.

MyPointer = &MyVariable; // Set MyPointer to point at MyVariable

  • Mit dem Präfix *, um den Wert zu erhalten, der an der Speicheradresse gespeichert ist, auf die verwiesen wird.

"MyPointer is pointing at " + *MyPointer;

Da ein Zeiger eine Variable ist, die eine Speicheradresse enthält, kann diese Speicheradresse in einer Zeigervariablen gespeichert werden.

Beispiel für den sorgfältigen und verantwortungsvollen Umgang mit Zeigern

    public unsafe void PointerTest()
    {
        int x = 100; // Create a variable named x

        int *MyPointer = &x; // Store the address of variable named x into the pointer named MyPointer

        textBox1.Text = ((int)MyPointer).ToString(); // Displays the memory address stored in pointer named MyPointer

        textBox2.Text = (*MyPointer).ToString(); // Displays the value of the variable named x via the pointer named MyPointer.

    }

Beachten Sie, dass der Typ des Zeigers ein int ist. Dies liegt daran, dass C # Speicheradressen als Ganzzahlen (int) interpretiert.

Warum ist es int statt uint?

Es gibt keinen guten Grund.

Warum Zeiger verwenden?

Zeiger machen viel Spaß. Da so viel Computer vom Speicher gesteuert wird, ermöglichen Zeiger einem Programmierer eine bessere Kontrolle über den Speicher seines Programms.

Speicherüberwachung.

Verwenden Sie Zeiger, um Speicherblöcke zu lesen und zu überwachen, wie sich die Werte, auf die gezeigt wird, im Laufe der Zeit ändern.

Ändern Sie diese Werte verantwortungsbewusst und verfolgen Sie, wie sich Ihre Änderungen auf Ihren Computer auswirken.

WonderWorker
quelle
2
65534scheint als Zeigerbereich sehr falsch zu sein. Sie sollten eine Referenz angeben.
Brent Bradburn
1
Ich habe dafür gestimmt, weil es ein großartiger Artikel ist, der Hinweise erklärt. Ich würde gerne einige Verweise auf die von Ihnen erwähnten Spezifikationen sehen (wie oben kommentiert). Jedoch; Diese Antwort hat nichts mit der Frage zu tun. Die Frage war ungefähr System.IntPtr. Ich würde gerne sehen, dass die Antwort am Ende aktualisiert wird und erklärt, was auch ein System.IntPtrund wie es sich auf unsichere Zeiger in C # bezieht.
Michael Puckett II
7

MSDN sagt uns:

Der IntPtr-Typ ist als Ganzzahl konzipiert, deren Größe plattformspezifisch ist. Das heißt, es wird erwartet, dass eine Instanz dieses Typs 32-Bit auf 32-Bit-Hardware und Betriebssystemen und 64-Bit auf 64-Bit-Hardware und Betriebssystemen ist.

Der IntPtr-Typ kann von Sprachen verwendet werden, die Zeiger unterstützen, und als allgemeines Mittel zum Verweisen auf Daten zwischen Sprachen, die Zeiger unterstützen oder nicht.

IntPtr-Objekte können auch zum Halten von Handles verwendet werden. Beispielsweise werden Instanzen von IntPtr in der System.IO.FileStream-Klasse häufig zum Speichern von Dateihandles verwendet.

Der IntPtr-Typ ist CLS-kompatibel, der UIntPtr-Typ jedoch nicht. In der Common Language Runtime wird nur der IntPtr-Typ verwendet. Der UIntPtr-Typ wird hauptsächlich bereitgestellt, um die architektonische Symmetrie mit dem IntPtr-Typ aufrechtzuerhalten.

http://msdn.microsoft.com/en-us/library/system.intptr(VS.71).aspx

Zyphrax
quelle
5

Nun, das ist die MSDN-Seite , die sich damit befasst IntPtr.

Die erste Zeile lautet:

Ein plattformspezifischer Typ, der zur Darstellung eines Zeigers oder eines Handles verwendet wird.

Was ein Zeiger oder Handle ist, heißt es auf der Seite:

Der IntPtr-Typ kann von Sprachen verwendet werden, die Zeiger unterstützen, und als allgemeines Mittel zum Verweisen auf Daten zwischen Sprachen, die Zeiger unterstützen oder nicht.

IntPtr-Objekte können auch zum Halten von Handles verwendet werden. Beispielsweise werden Instanzen von IntPtr in der System.IO.FileStream-Klasse häufig zum Speichern von Dateihandles verwendet.

Ein Zeiger ist eine Referenz auf einen Speicherbereich, der einige Daten enthält, an denen Sie interessiert sind.

Ein Handle kann eine Kennung für ein Objekt sein und wird zwischen Methoden / Klassen übergeben, wenn beide Seiten auf dieses Objekt zugreifen müssen.

ChrisF
quelle
2

Ein IntPtr ist ein Werttyp , der hauptsächlich zum Speichern von Speicheradressen oder Handles verwendet wird. Ein Zeiger ist eine Speicheradresse. Ein Zeiger kann typisiert (zB int*) oder untypisiert (zB void*) sein. Ein Windows-Handle ist ein Wert, der normalerweise dieselbe Größe (oder kleiner) als eine Speicheradresse hat und eine Systemressource darstellt (wie eine Datei oder ein Fenster).

cdiggins
quelle