Zeiger

Zeiger

# Zeiger für Array-Zugriff

Dieses Beispiel zeigt, wie Zeiger für C-ähnlichen Zugriff auf C#-Arrays verwendet werden können.

unsafe
{
    var buffer = new int[1024];
    fixed (int* p = &buffer[0])
    {
        for (var i = 0; i < buffer.Length; i++)
        {
            *(p + i) = i;
        }
    }
}

Der unsafe Das Schlüsselwort ist erforderlich, da der Zeigerzugriff keine Begrenzungsprüfungen ausgibt, die normalerweise beim regulären Zugriff auf C#-Arrays ausgegeben werden.

Die fixed weist den C#-Compiler an, Anweisungen auszugeben, um das Objekt auf ausnahmesichere Weise anzuheften. Pinning ist erforderlich, um sicherzustellen, dass der Garbage Collector das Array nicht im Speicher verschiebt, da dies alle Zeiger ungültig machen würde, die innerhalb des Arrays zeigen.

# Zeigerarithmetik

Addition und Subtraktion in Zeigern funktionieren anders als Ganzzahlen. Wenn ein Zeiger inkrementiert oder dekrementiert wird, wird die Adresse, auf die er zeigt, um die Größe des Referenztyps erhöht oder verringert.

Zum Beispiel der Typ int (Alias ​​für System.Int32 ) hat eine Größe von 4. Wenn ein int kann in Adresse 0, dem folgenden int gespeichert werden kann in Adresse 4 gespeichert werden, und so weiter. Im Code:

var ptr = (int*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 4
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8

Ebenso der Typ long (Alias ​​für System.Int64 ) hat eine Größe von 8. Wenn ein long kann in Adresse 0, dem nachfolgenden long gespeichert werden kann unter Adresse 8 gespeichert werden und so weiter. Im Code:

var ptr = (long*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr)); // prints 0
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 8
ptr++;
Console.WriteLine(new IntPtr(ptr)); // prints 16

Der Typ void ist speziell und void Zeiger sind ebenfalls etwas Besonderes und werden als Catch-All-Zeiger verwendet, wenn der Typ nicht bekannt ist oder keine Rolle spielt. Aufgrund ihrer größenunabhängigen Natur, void Zeiger können nicht inkrementiert oder dekrementiert werden:

var ptr = (void*)IntPtr.Zero;
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));
ptr++; // compile-time error
Console.WriteLine(new IntPtr(ptr));

# Der Stern ist Teil des Typs

In C und C++ ist das Sternchen in der Deklaration einer Zeigervariablen Teil des Ausdrucks deklariert werden. In C# ist das Sternchen in der Deklaration Teil des Typs .

In C, C++ und C# deklariert das folgende Snippet einen int Zeiger:

int* a;

In C und C++ deklariert das folgende Snippet einen int Zeiger und ein int Variable. In C# deklariert es zwei int Zeiger:

int* a, b; 

In C und C++ deklariert das folgende Snippet zwei int Zeiger. In C# ist es ungültig:

int *a, *b;

# ungültig*

C# erbt von C und C++ die Verwendung von void* als typ- und größenunabhängiger Zeiger.

void* ptr;

void* kann ein beliebiger Zeigertyp zugewiesen werden mit einer impliziten Konvertierung:

int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;

Die Umkehrung erfordert eine explizite Konvertierung:

int* p1 = (int*)IntPtr.Zero;
void* ptr = p1;
int* p2 = (int*)ptr;

# Mitgliedszugriff mit ->

C# erbt von C und C++ die Verwendung des Symbols -> als Mittel zum Zugriff auf die Mitglieder einer Instanz über einen typisierten Zeiger.

Betrachten Sie die folgende Struktur:

struct Vector2
{
    public int X;
    public int Y;
}

Dies ist ein Beispiel für die Verwendung von -> um auf seine Mitglieder zuzugreifen:

Vector2 v;
v.X = 5;
v.Y = 10;

Vector2* ptr = &v;
int x = ptr->X;
int y = ptr->Y;
string s = ptr->ToString();

Console.WriteLine(x); // prints 5
Console.WriteLine(y); // prints 10
Console.WriteLine(s); // prints Vector2

# Generische Zeiger

Die Kriterien, die ein Typ erfüllen muss, um Zeiger zu unterstützen (siehe Bemerkungen ) lässt sich nicht in Form allgemeiner Beschränkungen ausdrücken. Daher schlägt jeder Versuch fehl, einen Zeiger auf einen Typ zu deklarieren, der durch einen generischen Typparameter bereitgestellt wird.

void P<T>(T obj) 
    where T : struct
{
    T* ptr = &obj; // compile-time error
}

# Bemerkungen

# Zeiger und unsafe

Aufgrund ihrer Natur erzeugen Zeiger nicht verifizierbaren Code. Daher erfordert die Verwendung eines beliebigen Zeigertyps einen unsafe Kontext.

Der Typ System.IntPtr ist ein sicherer Wrapper um einen void* . Es ist als bequemere Alternative zu void* gedacht wenn ein unsicherer Kontext nicht anderweitig erforderlich ist, um die anstehende Aufgabe auszuführen.

# Undefiniertes Verhalten

Wie in C und C++ kann die falsche Verwendung von Zeigern ein undefiniertes Verhalten hervorrufen, mit möglichen Nebeneffekten wie Speicherbeschädigung und Ausführung von unbeabsichtigtem Code. Aufgrund der nicht verifizierbaren Natur der meisten Zeigeroperationen liegt die korrekte Verwendung von Zeigern ausschließlich in der Verantwortung des Programmierers.

# Typen, die Zeiger unterstützen

Im Gegensatz zu C und C++ haben nicht alle C#-Typen entsprechende Zeigertypen. Ein Typ T kann einen entsprechenden Zeigertyp haben, wenn beide der folgenden Kriterien zutreffen:

  • T ist ein Strukturtyp oder ein Zeigertyp.
  • T enthält nur Elemente, die diese beiden Kriterien rekursiv erfüllen.