Freigeben über


23 Unsicherer Code

23.1 Allgemein

Eine Implementierung, die unsicheren Code nicht unterstützt, ist erforderlich, um eine Verwendung der in dieser Klausel definierten syntaktischen Regeln zu diagnostizieren.

Der Rest dieser Klausel, einschließlich aller seiner Unterclauses, ist bedingt normativ.

Hinweis: Die C#-Kernsprache, wie in den vorherigen Klauseln definiert, unterscheidet sich insbesondere von C und C++ bei der Auslassung von Zeigern als Datentyp. Stattdessen stellt C# Verweise und die Möglichkeit zum Erstellen von Objekten bereit, die von einem Garbage Collector verwaltet werden. Dieses Design, gepaart mit anderen Features, macht C# zu einer viel sichereren Sprache als C oder C++. In der C#-Kernsprache ist es einfach nicht möglich, eine nicht initialisierte Variable, einen "dangling"-Zeiger oder einen Ausdruck zu haben, der ein Array über seine Grenzen hinaus indiziert. Ganze Kategorien von Fehlern, die routinemäßig C- und C++-Programme plagen, werden somit eliminiert.

Zwar verfügt praktisch jeder Zeigertypkonstrukt in C oder C++ über ein Referenztyp-Gegenstück in C#, aber es gibt Situationen, in denen der Zugriff auf Zeigertypen zu einer Notwendigkeit wird. Beispielsweise ist die Interoperabilität mit dem zugrunde liegenden Betriebssystem, der Zugriff auf ein speicherzuordnunges Gerät oder die Implementierung eines zeitkritischen Algorithmus möglicherweise nicht möglich oder praktisch ohne Zugriff auf Zeiger. Um diese Notwendigkeit zu beheben, bietet C# die Möglichkeit, unsicheren Code zu schreiben.

Im unsicheren Code ist es möglich, Zeiger zu deklarieren und zu betreiben, Konvertierungen zwischen Zeigern und integralen Typen durchzuführen, die Adresse von Variablen zu übernehmen usw. Das Schreiben unsicherer Code ähnelt dem Schreiben von C-Code in einem C#-Programm.

Unsicherer Code ist tatsächlich ein "sicheres" Feature aus der Perspektive von Entwicklern und Benutzern. Unsicherer Code muss eindeutig mit dem Modifizierer unsafegekennzeichnet sein, sodass Entwickler möglicherweise nicht versehentlich unsichere Features verwenden können, und das Ausführungsmodul funktioniert, um sicherzustellen, dass unsicherer Code nicht in einer nicht vertrauenswürdigen Umgebung ausgeführt werden kann.

Endnote

23.2 Unsichere Kontexte

Die unsicheren Features von C# sind nur in unsicheren Kontexten verfügbar. Ein unsicherer Kontext wird eingeführt, indem ein unsafe Modifizierer in die Deklaration eines Typs, eines Mitglieds oder einer lokalen Funktion oder durch Die Verwendung eines unsafe_statement eingeführt wird:

  • Eine Deklaration einer Klasse, Struktur, Schnittstelle oder Stellvertretung kann einen unsafe Modifizierer enthalten, in diesem Fall wird der gesamte textbezogene Umfang dieser Typdeklaration (einschließlich des Textkörpers der Klasse, der Struktur oder der Schnittstelle) als unsicherer Kontext betrachtet.

    Hinweis: Wenn die type_declaration teilweise ist, ist nur dieser Teil ein unsicherer Kontext. Endnote

  • Eine Deklaration eines Felds, einer Methode, einer Eigenschaft, eines Ereignisses, eines Indexers, eines Operators, eines Instanzkonstruktors, eines endgültigen, statischen Konstruktors oder einer lokalen Funktion kann einen unsafe Modifizierer enthalten, in diesem Fall wird der gesamte textbezogene Umfang dieser Memberdeklaration als unsicherer Kontext betrachtet.
  • Ein unsafe_statement ermöglicht die Verwendung eines unsicheren Kontexts innerhalb eines Blocks. Der gesamte textbezogene Umfang des zugeordneten Blocks wird als unsicherer Kontext betrachtet. Eine in einem unsicheren Kontext deklarierte lokale Funktion ist selbst unsicher.

Die zugehörigen Grammatikerweiterungen werden unten und in nachfolgenden Unterclauses angezeigt.

unsafe_modifier
    : 'unsafe'
    ;

unsafe_statement
    : 'unsafe' block
    ;

Beispiel: Im folgenden Code

public unsafe struct Node
{
    public int Value;
    public Node* Left;
    public Node* Right;
}

Der unsafe in der Strukturdeklaration angegebene Modifizierer bewirkt, dass der gesamte textbezogene Umfang der Strukturdeklaration zu einem unsicheren Kontext wird. Daher ist es möglich, die Left Felder Right als Zeigertyp zu deklarieren. Das obige Beispiel könnte auch geschrieben werden.

public struct Node
{
    public int Value;
    public unsafe Node* Left;
    public unsafe Node* Right;
}

Hier führen die unsafe Modifizierer in den Felddeklarationen dazu, dass diese Deklarationen als unsichere Kontexte betrachtet werden.

Endbeispiel

Abgesehen davon, dass ein unsicherer Kontext eingerichtet wird und die Verwendung von Zeigertypen erlaubt, hat der unsafe Modifizierer keine Auswirkungen auf einen Typ oder ein Element.

Beispiel: Im folgenden Code

public class A
{
    public unsafe virtual void F() 
    {
        char* p;
        ...
    }
}

public class B : A
{
    public override void F() 
    {
        base.F();
        ...
    }
}

Der unsichere Modifizierer für die F Methode A führt einfach dazu, dass der textbezogene Umfang F zu einem unsicheren Kontext wird, in dem die unsicheren Features der Sprache verwendet werden können. In der Außerkraftsetzung von F "in B" muss der unsafe Modifizierer nicht erneut angegeben werden, es sei denn, die F Methode B selbst benötigt Zugriff auf unsichere Features.

Die Situation unterscheidet sich geringfügig, wenn ein Zeigertyp Teil der Signatur der Methode ist.

public unsafe class A
{
    public virtual void F(char* p) {...}
}

public class B: A
{
    public unsafe override void F(char* p) {...}
}

Da Fdie Signatur einen Zeigertyp enthält, kann sie nur in unsicherem Kontext geschrieben werden. Der unsichere Kontext kann jedoch eingeführt werden, indem entweder die gesamte Klasse nicht sicher ist, wie dies der Fall Aist, oder durch Einschließen eines unsafe Modifizierers in die Methodendeklaration, wie dies der BFall ist.

Endbeispiel

Wenn der unsafe Modifizierer für eine partielle Typdeklaration (§15.2.7) verwendet wird, wird nur dieser bestimmte Teil als unsicherer Kontext betrachtet.

23.3 Zeigertypen

In einem unsicheren Kontext kann ein Typ (§8.1) ein pointer_type sowie ein value_type, ein reference_type oder ein type_parameter sein. In einem unsicheren Kontext kann ein pointer_type auch der Elementtyp eines Arrays (§17) sein. Ein pointer_type kann auch in einem Typofausdruck (§12.8.18) außerhalb eines unsicheren Kontexts verwendet werden (z. B. die Verwendung ist nicht sicher).

Eine pointer_type wird als unmanaged_type (§8.8) oder das Schlüsselwort voidgeschrieben, gefolgt von einem * Token:

pointer_type
    : value_type ('*')+
    | 'void' ('*')+
    ;

Der vor dem * Zeigertyp angegebene Typ wird als Referenttyp des Zeigertyps bezeichnet. Sie stellt den Typ der Variablen dar, auf die ein Wert des Zeigertyps verweist.

Eine pointer_type darf nur in einem array_type in einem unsicheren Kontext (§23.2) verwendet werden. Ein non_array_type ist jeder Typ, der selbst keine array_type ist.

Im Gegensatz zu Verweisen (Werte von Bezugstypen) werden Zeiger nicht vom Garbage Collector nachverfolgt. Der Garbage Collector verfügt über keine Kenntnisse von Zeigern und daten, auf die sie verweisen. Aus diesem Grund darf ein Zeiger nicht auf einen Verweis oder eine Struktur verweisen, die Verweise enthält, und der Referenttyp eines Zeigers muss ein unmanaged_type sein. Zeigertypen selbst sind nicht verwaltete Typen, daher kann ein Zeigertyp als Referenttyp für einen anderen Zeigertyp verwendet werden.

Die intuitive Regel zum Vermischen von Zeigern und Verweisen besteht darin, dass Referenten von Verweisen (Objekten) Zeiger enthalten dürfen, aber Referenten von Zeigern dürfen keine Verweise enthalten.

Beispiel: Einige Beispiele für Zeigertypen werden in der folgenden Tabelle angegeben:

Beispiel Beschreibung
byte* Zeiger auf byte
char* Zeiger auf char
int** Zeiger auf Zeiger auf int
int*[] Eindimensionales Array von Zeigern auf int
void* Zeiger auf unbekannten Typ

Endbeispiel

Für eine bestimmte Implementierung müssen alle Zeigertypen dieselbe Größe und Darstellung aufweisen.

Hinweis: Im Gegensatz zu C und C++ wird bei der Deklaration mehrerer Zeiger in C# nur der * zugrunde liegende Typ geschrieben, nicht als Präfixzeichenzeichen für jeden Zeigernamen. Zum Beispiel:

int* pi, pj; // NOT as int *pi, *pj;  

Endnote

Der Wert eines Zeigers mit Typ T* stellt die Adresse einer Variablen vom Typ Tdar. Der Zeigerdereferenzierungsoperator * (§23.6.2) kann für den Zugriff auf diese Variable verwendet werden.

Beispiel: Bei einer Variablen P vom Typ int*gibt der Ausdruck *P die Variable an, die int an der adresse Penthalten ist. Endbeispiel

Wie ein Objektverweis kann ein Zeiger sein null. Das Anwenden des Dereferenzierungsoperators auf einen null-wertigen Zeiger führt zu einem implementierungsdefinierten Verhalten (§23.6.2). Ein Zeiger mit Wert null wird durch alle Bits-Null dargestellt.

Der void* Typ stellt einen Zeiger auf einen unbekannten Typ dar. Da der Referenttyp unbekannt ist, kann der Dereferenzierungsoperator nicht auf einen Zeiger des Typs void*angewendet werden, und es kann auch keine Arithmetik für einen solchen Zeiger ausgeführt werden. Ein Zeiger vom Typ void* kann jedoch in einen beliebigen anderen Zeigertyp (und umgekehrt) und mit Werten anderer Zeigertypen (§23.6.8) verglichen werden.

Zeigertypen sind eine separate Kategorie von Typen. Im Gegensatz zu Bezugstypen und Werttypen erben Zeigertypen nicht und object es sind keine Konvertierungen zwischen Zeigertypen und object. Insbesondere werden Boxen und Entboxen (§8.3.13) für Zeiger nicht unterstützt. Konvertierungen sind jedoch zwischen verschiedenen Zeigertypen und zwischen Zeigertypen und den integralen Typen zulässig. Dies wird in §23.5 beschrieben.

Ein pointer_type kann nicht als Typargument (§8.4) verwendet werden, und die Typableitung (§12.6.3) schlägt bei generischen Methodenaufrufen fehl, die ein Typargument als Zeigertyp abgeleitet haben.

Ein pointer_type kann nicht als Typ eines Unterausdrucks eines dynamisch gebundenen Vorgangs (§12.3.3) verwendet werden.

Ein pointer_type kann nicht als Typ des ersten Parameters in einer Erweiterungsmethode (§15.6.10) verwendet werden.

Ein pointer_type kann als Typ eines veränderliche Felds (§15.5.4) verwendet werden.

Die dynamische Löschung eines Typs E* ist der Zeigertyp mit dem Referenztyp der dynamischen Löschung von E.

Ein Ausdruck mit einem Zeigertyp kann nicht verwendet werden, um den Wert in einem member_declarator innerhalb eines anonymous_object_creation_expression bereitzustellen (§12.8.17.7).

Der Standardwert (§9.3) für jeden Zeigertyp ist null.

Hinweis: Obwohl Zeiger als Nachverweisparameter übergeben werden können, kann dies zu einem nicht definierten Verhalten führen, da der Zeiger möglicherweise gut auf eine lokale Variable verweist, die nicht mehr vorhanden ist, wenn die aufgerufene Methode zurückgegeben wird, oder das feste Objekt, auf das sie verweist, nicht mehr behoben ist. Zum Beispiel:

class Test
{
    static int value = 20;

    unsafe static void F(out int* pi1, ref int* pi2) 
    {
        int i = 10;
        pi1 = &i;       // return address of local variable
        fixed (int* pj = &value)
        {
            // ...
            pi2 = pj;   // return address that will soon not be fixed
        }
    }

    static void Main()
    {
        int i = 15;
        unsafe 
        {
            int* px1;
            int* px2 = &i;
            F(out px1, ref px2);
            int v1 = *px1; // undefined
            int v2 = *px2; // undefined
        }
    }
}

Endnote

Eine Methode kann einen Wert eines Typs zurückgeben, und dieser Typ kann ein Zeiger sein.

Beispiel: Wenn ein Zeiger auf eine zusammenhängende Abfolge von intn, der Elementanzahl dieser Sequenz und ein anderer int Wert gegeben wird, gibt die folgende Methode die Adresse dieses Werts in dieser Sequenz zurück, wenn eine Übereinstimmung auftritt; andernfalls wird folgendes zurückgegeben null:

unsafe static int* Find(int* pi, int size, int value)
{
    for (int i = 0; i < size; ++i)
    {
        if (*pi == value)
        {
            return pi;
        }
        ++pi;
    }
    return null;
}

Endbeispiel

In einem unsicheren Kontext stehen mehrere Konstrukte für das Arbeiten auf Zeigern zur Verfügung:

  • Der unäre * Operator kann zum Ausführen einer Zeigerdereferenzierung (§23.6.2) verwendet werden.
  • Der -> Operator kann für den Zugriff auf ein Mitglied einer Struktur über einen Zeiger (§23.6.3) verwendet werden.
  • Der [] Operator kann zum Indizieren eines Zeigers (§23.6.4) verwendet werden.
  • Der unäre & Operator kann verwendet werden, um die Adresse einer Variablen zu erhalten (§23.6.5).
  • Die ++ Operatoren -- können verwendet werden, um Zeiger zu erhöhen und zu erhöhen (§23.6.6).
  • Die Binären + und - Operatoren können zum Ausführen von Zeigerarithmetik (§23.6.7) verwendet werden.
  • Die ==, !=, <, >, <=und >= Operatoren können zum Vergleichen von Zeigern (§23.6.8) verwendet werden.
  • Der stackalloc Operator kann verwendet werden, um Speicher aus dem Aufrufstapel (§23.9) zuzuweisen.
  • Die fixed Anweisung kann verwendet werden, um eine Variable vorübergehend zu korrigieren, damit ihre Adresse abgerufen werden kann (§23.7).

23.4 Feste und verschiebebare Variablen

Die Adresse des Operators (§23.6.5) und die fixed Anweisung (§23.7) unterteilen Variablen in zwei Kategorien: Feste Variablen und verschiebbare Variablen.

Feste Variablen befinden sich an Speicherorten, die vom Betrieb des Garbage Collector nicht betroffen sind. (Beispiele für feste Variablen sind lokale Variablen, Wertparameter und Variablen, die durch Dieeferencing-Zeiger erstellt wurden.) Auf der anderen Seite befinden sich verschiebefähige Variablen an Speicherorten, die der Verlagerung oder Entsorgung durch den Garbage Collector unterliegen. (Beispiele für verschiebebare Variablen sind Felder in Objekten und Elementen von Arrays.)

Der & Operator (§23.6.5) erlaubt, die Adresse einer festen Variablen ohne Einschränkungen zu erhalten. Da eine verschiebebare Variable jedoch der Verlagerung oder Entsorgung durch den Garbage Collector unterliegt, kann die Adresse einer verschiebebaren Variablen nur mithilfe einer fixed statement (§23.7) abgerufen werden, und diese Adresse bleibt nur für die Dauer dieser fixed Anweisung gültig.

Präzise ausgedrückt ist eine feste Variable eine der folgenden:

Alle anderen Variablen werden als verschiebebare Variablen klassifiziert.

Ein statisches Feld wird als verschiebebare Variable klassifiziert. Außerdem wird ein By-Reference-Parameter als verschiebebare Variable klassifiziert, auch wenn das für den Parameter angegebene Argument eine feste Variable ist. Schließlich wird eine Variable, die durch das Ableiten eines Zeigers erzeugt wird, immer als feste Variable klassifiziert.

23.5 Zeigerkonvertierungen

23.5.1 Allgemein

In einem unsicheren Kontext wird der Satz der verfügbaren impliziten Konvertierungen (§10.2) erweitert, um die folgenden impliziten Zeigerkonvertierungen einzuschließen:

  • Von einem beliebigen pointer_type in den Typ void*.
  • null Vom Literal (§6.4.5.7) bis zu einem beliebigen pointer_type.

Darüber hinaus wird der Satz verfügbarer expliziter Konvertierungen (§10.3) in einem unsicheren Kontext erweitert, um die folgenden expliziten Zeigerkonvertierungen einzuschließen:

  • Von jedem pointer_type bis hin zu allen anderen pointer_type.
  • Von sbyte, , byte, short, intushort, , uint, oder ulong longzu einer beliebigen pointer_type.
  • Von jeder pointer_type bis zu sbyte, byte, short, ushort, int, , , uint, , oder ulonglong.

Schließlich enthält der Satz impliziter Standardkonvertierungen (§10.4.2) in einem unsicheren Kontext die folgenden Zeigerkonvertierungen:

  • Von einem beliebigen pointer_type in den Typ void*.
  • null Vom Literal bis zu einem beliebigen pointer_type.

Konvertierungen zwischen zwei Zeigertypen ändern niemals den tatsächlichen Zeigerwert. Anders ausgedrückt: Eine Konvertierung von einem Zeigertyp in einen anderen hat keine Auswirkung auf die zugrunde liegende Adresse, die vom Zeiger angegeben wird.

Wenn ein Zeigertyp in einen anderen konvertiert wird, wenn der resultierende Zeiger nicht ordnungsgemäß für den Zeigertyp ausgerichtet ist, wird das Verhalten nicht definiert, wenn das Ergebnis abgeleitet wird. Im Allgemeinen ist das Konzept "richtig ausgerichtet" transitiv: Wenn ein Zeiger zum Typ A korrekt ausgerichtet ist, der für einen Zeiger Brichtig ausgerichtet ist, der wiederum für einen Zeiger korrekt ausgerichtet ist, um einen Zutyp einzugeben C, wird ein Zeiger zum Eingeben A korrekt ausgerichtet, damit ein Zeiger zum Eingeben Ceines Zeigers richtig ausgerichtet wird.

Beispiel: Berücksichtigen Sie den folgenden Fall, in dem über einen Zeiger auf einen anderen Typ auf eine Variable zugegriffen wird:

unsafe static void M()
{
    char c = 'A';
    char* pc = &c;
    void* pv = pc;
    int* pi = (int*)pv; // pretend a 16-bit char is a 32-bit int
    int i = *pi;        // read 32-bit int; undefined
    *pi = 123456;       // write 32-bit int; undefined
}

Endbeispiel

Wenn ein Zeigertyp in einen Zeiger bytekonvertiert wird, verweist das Ergebnis auf die niedrigste Adresszahl byte der Variablen. Nachfolgende Inkremente des Ergebnisses bis zur Größe der Variablen liefern Zeiger auf die verbleibenden Bytes dieser Variablen.

Beispiel: Die folgende Methode zeigt jede der acht Bytes in einem double hexadezimalen Wert an:

class Test
{
    static void Main()
    {
        double d = 123.456e23;
        unsafe
        {
            byte* pb = (byte*)&d;
            for (int i = 0; i < sizeof(double); ++i)
            {
                Console.Write($" {*pb++:X2}");
            }
            Console.WriteLine();
        }
    }
}

Natürlich hängt die erzeugte Ausgabe von Endianität ab. Eine Möglichkeit ist " BA FF 51 A2 90 6C 24 45".

Endbeispiel

Zuordnungen zwischen Zeigern und ganzzahlen sind implementierungsdefiniert.

Hinweis: Bei 32- und 64-Bit-CPU-Architekturen mit einem linearen Adressraum verhalten sich Konvertierungen von Zeigern in oder von integralen Typen in der Regel genau wie Konvertierungen oder uint Werte in oder ulong aus diesen integralen Typen. Endnote

23.5.2 Zeigerarrays

Arrays von Zeigern können mithilfe von array_creation_expression (§12.8.17.5) in einem unsicheren Kontext erstellt werden. Nur einige der Konvertierungen, die für andere Arraytypen gelten, sind für Zeigerarrays zulässig:

  • Die implizite Verweiskonvertierung (§10.2.8) von jedem array_type in System.Array und die von ihr implementierten Schnittstellen gelten auch für Zeigerarrays. Jeder Versuch, auf die Arrayelemente über System.Array oder die schnittstellen zuzugreifen, die es implementiert, kann jedoch zur Laufzeit zu einer Ausnahme führen, da Zeigertypen nicht zu konvertieren objectsind.
  • Die impliziten und expliziten Verweiskonvertierungen (§10.2.8, §10.3.5) von einem eindimensionalen Arraytyp S[] in System.Collections.Generic.IList<T> und ihre generischen Basisschnittstellen gelten niemals für Zeigerarrays.
  • Die explizite Verweiskonvertierung (§10.3.5) und die Schnittstellen, die System.Array sie für alle array_type implementieren, gelten für Zeigerarrays.
  • Die expliziten Verweiskonvertierungen (§10.3.5) von System.Collections.Generic.IList<S> und deren Basisschnittstellen in einen eindimensionalen Arraytyp T[] gelten niemals für Zeigerarrays, da Zeigertypen nicht als Typargumente verwendet werden können, und es gibt keine Konvertierungen von Zeigertypen zu Nicht-Zeigertypen.

Diese Einschränkungen bedeuten, dass die Erweiterung für die Anweisung über Arrays, die foreach in §9.4.4.17 beschrieben sind, nicht auf Zeigerarrays angewendet werden kann. Stattdessen eine foreach Anweisung des Formulars

foreach (V v in x)embedded_statement

dabei ist der Typ eines x Arraytyps des Formulars T[,,...,], n die Anzahl der Dimensionen minus 1 und T oder V ein Zeigertyp, wird mithilfe geschachtelter Forschleifen wie folgt erweitert:

{
    T[,,...,] a = x;
    for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++)
    {
        for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++)
        {
            ...
            for (int in = a.GetLowerBound(n); in <= a.GetUpperBound(n); in++) 
            {
                V v = (V)a[i0,i1,...,in];
                *embedded_statement*
            }
        }
    }
}

Die Variablen a, , i0, i1... insind für oder den embedded_statement oder einen anderen Quellcode des Programms nicht sichtbar oder zugänglichx. Die Variable v ist in der eingebetteten Anweisung schreibgeschützt. Wenn es keine explizite Konvertierung (§23.5) von T (dem Elementtyp) Vin gibt, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. Wenn x der Wert nullvorhanden ist, wird zur Laufzeit ein System.NullReferenceException Fehler ausgelöst.

Hinweis: Obwohl Zeigertypen nicht als Typargumente zulässig sind, können Zeigerarrays als Typargumente verwendet werden. Endnote

23.6 Zeiger in Ausdrücken

23.6.1 Allgemein

In einem unsicheren Kontext kann ein Ausdruck ein Ergebnis eines Zeigertyps liefern, aber außerhalb eines unsicheren Kontexts ist es ein Kompilierungszeitfehler für einen Ausdruck, der vom Zeigertyp sein soll. Wenn ein simple_name (§12.8.4) oder member_access (§12.8.12) member_access (§12.8.7), invocation_expression (§12.8.10) oder element_access (§12.8.12) einen Zeigertyp aufweist.

In einem unsicheren Kontext ermöglichen die primary_no_array_creation_expression (§12.8) und unary_expression (§12.9) zusätzliche Konstrukte, die in den folgenden Unterclauses beschrieben werden.

Hinweis: Die Rangfolge und Zuordnung der unsicheren Operatoren wird durch die Grammatik impliziert. Endnote

23.6.2 Zeigerreferenz

Ein pointer_indirection_expression besteht aus einem Sternchen (*) gefolgt von einem unary_expression.

pointer_indirection_expression
    : '*' unary_expression
    ;

Der unäre * Operator bezeichnet die Zeigerdereferenzierung und wird verwendet, um die Variable abzurufen, auf die ein Zeiger zeigt. Das Ergebnis der Auswertung *P, wobei P ein Ausdruck eines Zeigertyps T*ist, ist eine Variable vom Typ T. Es handelt sich um einen Kompilierungszeitfehler, um den unären Operator auf einen Ausdruck vom Typ void* oder auf einen Ausdruck anzuwenden, der nicht vom Zeigertyp * stammt.

Der Effekt der Anwendung des unären * Operators auf einen null-wertigen Zeiger ist implementierungsdefiniert. Insbesondere gibt es keine Garantie dafür, dass dieser Vorgang eine System.NullReferenceException.

Wenn dem Zeiger ein ungültiger Wert zugewiesen wurde, ist das Verhalten des nicht ären * Operators nicht definiert.

Hinweis: Unter den ungültigen Werten zum Ableiten eines Zeigers durch den unären * Operator ist eine Adresse, die für den Typ, auf den verwiesen wird, unangemessen ausgerichtet ist (siehe Beispiel in §23.5) und die Adresse einer Variablen nach dem Ende der Lebensdauer.

Für die Zwecke der endgültigen Zuordnungsanalyse wird eine Variable, die durch die Auswertung eines Ausdrucks des Formulars *P erzeugt wird, zunächst als anfangs zugewiesen (§9.4.2) betrachtet.

23.6.3 Zugriff auf Zeigermitglied

Ein pointer_member_access besteht aus einem primary_expression, gefolgt von einem "->"-Token, gefolgt von einem Bezeichner und einem optionalen type_argument_list.

pointer_member_access
    : primary_expression '->' identifier type_argument_list?
    ;

Bei einem Zeigerelementzugriff auf das Formular P->IP muss es sich um einen Ausdruck eines Zeigerstyps handeln und I ein barrierefreies Element des Typs bezeichnen, auf den P die Punkte zugegriffen werden kann.

Ein Zeigermememmzugriff des Formulars P->I wird genau so ausgewertet wie (*P).I. Eine Beschreibung des Zeiger-Dereferenzierungsoperators (*) finden Sie unter §23.6.2. Eine Beschreibung des Mitgliedszugriffsanbieters (.) finden Sie unter §12.8.7.

Beispiel: Im folgenden Code

struct Point
{
    public int x;
    public int y;
    public override string ToString() => $"({x},{y})";
}

class Test
{
    static void Main()
    {
        Point point;
        unsafe
        {
            Point* p = &point;
            p->x = 10;
            p->y = 20;
            Console.WriteLine(p->ToString());
        }
    }
}

Der -> Operator wird verwendet, um auf Felder zuzugreifen und eine Methode einer Struktur über einen Zeiger aufzurufen. Da der Vorgang P->I genau gleichbedeutend (*P).Iist, könnte die Main Methode ebenso gut geschrieben worden sein:

class Test
{
    static void Main()
    {
        Point point;
        unsafe
        {
            Point* p = &point;
            (*p).x = 10;
            (*p).y = 20;
            Console.WriteLine((*p).ToString());
        }
    }
}

Endbeispiel

23.6.4 Zugriff auf Zeigerelemente

Ein pointer_element_access besteht aus einer primary_no_array_creation_expression gefolgt von einem Ausdruck, der in "[" und "]" eingeschlossen ist.

pointer_element_access
    : primary_no_array_creation_expression '[' expression ']'
    ;

Bei einem Zeigerelementzugriff auf das Formular P[E]P muss es sich um einen Ausdruck eines anderen Zeigertyps als void*, und E es handelt sich um einen Ausdruck, der implizit in int, uint, , longoder ulong.

Ein Zeigerelementzugriff auf das Formular P[E] wird genau als *(P + E)ausgewertet. Eine Beschreibung des Zeiger-Dereferenzierungsoperators (*) finden Sie unter §23.6.2. Eine Beschreibung des Zeigerzugabeoperators (+) finden Sie unter §23.6.7.

Beispiel: Im folgenden Code

class Test
{
    static void Main()
    {
        unsafe
        {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++)
            {
                p[i] = (char)i;
            }
        }
    }
}

Ein Zeigerelementzugriff wird verwendet, um den Zeichenpuffer in einer for Schleife zu initialisieren. Da der Vorgang P[E] genau gleichbedeutend *(P + E)ist, könnte das Beispiel genauso gut geschrieben worden sein:

class Test
{
    static void Main()
    {
        unsafe
        {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++)
            {
                *(p + i) = (char)i;
            }
        }
    }
}

Endbeispiel

Der Zeigerelementzugriffsoperator überprüft nicht auf Fehler außerhalb der Grenzen, und das Verhalten beim Zugriff auf ein out-of-bounds-Element ist nicht definiert.

Hinweis: Dies ist identisch mit C und C++. Endnote

23.6.5 Die Adresse des Betreibers

Ein addressof_expression besteht aus einem kaufmännischen Und-Zeichen (&) gefolgt von einem unary_expression.

addressof_expression
    : '&' unary_expression
    ;

Bei einem Ausdruck E , der einen Typ T aufweist und als feste Variable (§23.4) klassifiziert wird, berechnet das Konstrukt &E die Adresse der variablen, die angegeben Ewird. Der Typ des Ergebnisses wird T* als Wert klassifiziert. Ein Kompilierungszeitfehler tritt auf, wenn E er nicht als Variable klassifiziert wird, wenn E er als schreibgeschützte lokale Variable klassifiziert wird oder E eine verschiebebare Variable angibt. Im letzten Fall kann eine feste Anweisung (§23.7) verwendet werden, um die Variable vorübergehend zu "fixieren", bevor sie ihre Adresse abrufen.

Hinweis: Wie in §12.8.7 angegeben, gilt dieses Feld als Wert, nicht als Variable, außerhalb eines Instanzkonstruktors oder statischen Konstruktors für eine Struktur oder Klasse, die ein readonly Feld definiert. Daher kann seine Adresse nicht genommen werden. Ebenso kann die Adresse einer Konstante nicht übernommen werden. Endnote

Der & Operator erfordert nicht, dass das Argument definitiv zugewiesen wird, aber nach einem & Vorgang wird die Variable, auf die der Operator angewendet wird, in dem Ausführungspfad, in dem der Vorgang auftritt, definitiv zugewiesen. Es liegt in der Verantwortung des Programmierers, sicherzustellen, dass die korrekte Initialisierung der Variablen tatsächlich in dieser Situation stattfindet.

Beispiel: Im folgenden Code

class Test
{
    static void Main()
    {
        int i;
        unsafe
        {
            int* p = &i;
            *p = 123;
        }
        Console.WriteLine(i);
    }
}

i wird als definitiv zugewiesen nach dem Vorgang, der &i zum Initialisieren pverwendet wird. Die Zuweisung, die *p wirksam initialisiert iwird, aber die Einbeziehung dieser Initialisierung liegt in der Verantwortung des Programmierers, und es würde kein Kompilierungszeitfehler auftreten, wenn die Zuordnung entfernt wurde.

Endbeispiel

Hinweis: Die Regeln der eindeutigen Zuordnung für den & Operator sind so vorhanden, dass eine redundante Initialisierung lokaler Variablen vermieden werden kann. Beispielsweise verwenden viele externe APIs einen Zeiger auf eine Struktur, die von der API ausgefüllt wird. Aufrufe solcher APIs übergeben in der Regel die Adresse einer lokalen Strukturvariable, und ohne die Regel wäre eine redundante Initialisierung der Strukturvariable erforderlich. Endnote

Hinweis: Wenn eine lokale Variable, ein Wertparameter oder ein Parameterarray von einer anonymen Funktion (§12.8.24) erfasst wird, wird diese lokale Variable, ein Parameter oder ein Parameterarray nicht mehr als feste Variable (§23.7) betrachtet, sondern stattdessen als verschiebebare Variable betrachtet. Daher ist es ein Fehler für jeden unsicheren Code, die Adresse einer lokalen Variablen, eines Wertparameters oder eines Parameterarrays zu übernehmen, das von einer anonymen Funktion erfasst wurde. Endnote

23.6.6 Zeiger inkrementierung und Dekrementierung

In unsicheren Kontexten können die ++ Und -- Operatoren (§12.8.16 und §12.9.6) auf Zeigervariablen aller Typen angewendet werden, mit Ausnahme void*von . Daher werden für jeden Zeigertyp T*die folgenden Operatoren implizit definiert:

T* operator ++(T* x);
T* operator --(T* x);

Die Operatoren erzeugen die gleichen Ergebnisse wie x+1 bzw. (§23.6.7x-1). Anders ausgedrückt: Bei einer Zeigervariablen vom Typ T*fügt sizeof(T) der ++ Operator der in der Variablen enthaltenen Adresse hinzu, und der -- Operator subtrahiert sizeof(T) von der in der Variablen enthaltenen Adresse.

Wenn ein Zeigervorgang inkrementiert oder dekrementiert wird, wird die Domäne des Zeigertyps überlaufen, wird das Ergebnis implementierungsdefiniert, es werden jedoch keine Ausnahmen erstellt.

23.6.7 Zeigerarithmetik

In einem unsicheren Kontext kann der + Operator (§12.10.5) und - der Operator (§12.10.6) auf Werte aller Zeigertypen angewendet werden, mit Ausnahme void*von . Daher werden für jeden Zeigertyp T*die folgenden Operatoren implizit definiert:

T* operator +(T* x, int y);
T* operator +(T* x, uint y);
T* operator +(T* x, long y);
T* operator +(T* x, ulong y);
T* operator +(int x, T* y);
T* operator +(uint x, T* y);
T* operator +(long x, T* y);
T* operator +(ulong x, T* y);
T* operator –(T* x, int y);
T* operator –(T* x, uint y);
T* operator –(T* x, long y);
T* operator –(T* x, ulong y);
long operator –(T* x, T* y);

Ein Ausdruck P eines Zeigertyps T* und eines Ausdrucks N vom Typ int, uint, , longoder ulong, die Ausdrücke und N + P berechnen den Zeigerwert des Typs T* P + N, der aus dem Hinzufügen N * sizeof(T) zu der von .P Ebenso berechnet der Ausdruck P – N den Zeigerwert des Typs T* , der aus dem Subtrahieren N * sizeof(T) von der angegebenen PAdresse resultiert.

Bei zwei Ausdrücken und , eines Zeigertyps P T*, berechnet der Ausdruck P – Q den Unterschied zwischen den adressen, die angegeben werdenP, und Q dividiert dann diesen Unterschied durch sizeof(T).Q Der Typ des Ergebnisses ist immer long. In Kraft wird P - Q berechnet als ((long)(P) - (long)(Q)) / sizeof(T).

Beispiel:

class Test
{
    static void Main()
    {
        unsafe
        {
            int* values = stackalloc int[20];
            int* p = &values[1];
            int* q = &values[15];
            Console.WriteLine($"p - q = {p - q}");
            Console.WriteLine($"q - p = {q - p}");
        }
    }
}

die Ausgabe erzeugt:

p - q = -14
q - p = 14

Endbeispiel

Wenn ein Zeigerarithmetischer Vorgang die Domäne des Zeigertyps überläuft, wird das Ergebnis in einer implementierungsdefinierten Weise abgeschnitten, es werden jedoch keine Ausnahmen erzeugt.

23.6.8 Zeigervergleich

In einem unsicheren Kontext können die ==Operatoren , , !=, <>, <=und Operatoren >= (§12.12) auf Werte aller Zeigertypen angewendet werden. Die Zeigervergleichsoperatoren sind:

bool operator ==(void* x, void* y);
bool operator !=(void* x, void* y);
bool operator <(void* x, void* y);
bool operator >(void* x, void* y);
bool operator <=(void* x, void* y);
bool operator >=(void* x, void* y);

Da eine implizite Konvertierung von jedem Zeigertyp in den void* Typ vorhanden ist, können Operanden eines beliebigen Zeigertyps mit diesen Operatoren verglichen werden. Die Vergleichsoperatoren vergleichen die von den beiden Operanden angegebenen Adressen so, als wären sie nicht signierte ganze Zahlen.

23.6.9 Der Sizeof-Operator

Für bestimmte vordefinierte Typen (§12.8.19) gibt der sizeof Operator einen konstanten int Wert zurück. Für alle anderen Typen ist das Ergebnis des sizeof Operators implementierungsdefiniert und wird als Wert und nicht als Konstante klassifiziert.

Die Reihenfolge, in der Mitglieder in eine Struktur verpackt sind, ist nicht angegeben.

Für Ausrichtungszwecke kann es am Anfang einer Struktur, innerhalb einer Struktur und am Ende der Struktur unbenannt sein. Der Inhalt der Bits, die als Abstand verwendet werden, sind unbestimmt.

Wenn sie auf einen Operanden mit Strukturtyp angewendet wird, ist das Ergebnis die Gesamtanzahl der Bytes in einer Variablen dieses Typs, einschließlich aller Abstände.

23.7 Die feste Anweisung

In einem unsicheren Kontext lässt die embedded_statement (§13.1)-Produktion ein zusätzliches Konstrukt zu, die feste Anweisung, die zum "Fixieren" einer verschiebebaren Variablen verwendet wird, sodass ihre Adresse für die Dauer der Anweisung konstant bleibt.

fixed_statement
    : 'fixed' '(' pointer_type fixed_pointer_declarators ')' embedded_statement
    ;

fixed_pointer_declarators
    : fixed_pointer_declarator (','  fixed_pointer_declarator)*
    ;

fixed_pointer_declarator
    : identifier '=' fixed_pointer_initializer
    ;

fixed_pointer_initializer
    : '&' variable_reference
    | expression
    ;

Jede fixed_pointer_declarator deklariert eine lokale Variable der angegebenen pointer_type und initialisiert diese lokale Variable mit der adresse, die vom entsprechenden fixed_pointer_initializer berechnet wird. Auf eine in einer festen Anweisung deklarierte lokale Variable kann in allen fixed_pointer_initializer, die rechts von der Deklaration dieser Variablen und im embedded_statement der festen Anweisung auftreten, zugegriffen werden. Eine lokale Variable, die von einer festen Anweisung deklariert wird, gilt als schreibgeschützt. Ein Kompilierungszeitfehler tritt auf, wenn die eingebettete Anweisung versucht, diese lokale Variable (über zuordnung oder die ++ Operatoren -- ) zu ändern oder als Referenz- oder Ausgabeparameter zu übergeben.

Fehler bei der Verwendung einer erfassten lokalen Variablen (§12.19.6.2), Wertparameter oder Parameterarray in einem fixed_pointer_initializer. Eine fixed_pointer_initializer kann eine der folgenden Sein:

  • Das Token "&" gefolgt von einem variable_reference (§9.5) zu einer verschiebebaren Variablen (§23.4) eines nicht verwalteten Typs T, vorausgesetzt, der Typ T* ist implizit in den in der fixed Anweisung angegebenen Zeigertyp konvertierbar. In diesem Fall berechnet der Initialisierer die Adresse der angegebenen Variablen, und die Variable bleibt garantiert bei einer festen Adresse für die Dauer der festen Anweisung.
  • Ein Ausdruck einer array_type mit Elementen eines nicht verwalteten Typs T, vorausgesetzt, der Typ T* ist implizit in den in der festen Anweisung angegebenen Zeigertyp konvertierbar. In diesem Fall berechnet der Initialisierer die Adresse des ersten Elements im Array, und das gesamte Array bleibt garantiert bei einer festen Adresse für die Dauer der fixed Anweisung. Wenn der Arrayausdruck null-Elemente aufweist null oder wenn das Array Nullelemente enthält, berechnet der Initialisierer eine Adresse, die gleich Null ist.
  • Ein Ausdruck vom Typ string, vorausgesetzt, der Typ char* ist implizit in den in der fixed Anweisung angegebenen Zeigertyp konvertierbar. In diesem Fall berechnet der Initialisierer die Adresse des ersten Zeichens in der Zeichenfolge, und die gesamte Zeichenfolge bleibt garantiert bei einer festen Adresse für die Dauer der fixed Anweisung. Das Verhalten der fixed Anweisung ist implementierungsdefiniert, wenn der Zeichenfolgenausdruck lautet null.
  • Ein anderer Ausdruck als array_type oder , vorausgesetzt, es gibt eine barrierefreie Methode oder barrierefreie Erweiterungsmethode, die der Signatur ref [readonly] T GetPinnableReference()entspricht, wobei T ein unmanaged_type ist und T* implizit in den in der fixed Anweisung angegebenen Zeigertyp konvertierbar ist.string In diesem Fall berechnet der Initialisierer die Adresse der zurückgegebenen Variablen, und diese Variable bleibt garantiert bei einer festen Adresse für die Dauer der fixed Anweisung. Eine GetPinnableReference() Methode kann von der fixed Anweisung verwendet werden, wenn die Überladungsauflösung (§12.6.4) genau ein Funktionselement erzeugt, und dieses Funktionselement erfüllt die vorherigen Bedingungen. Die GetPinnableReference Methode sollte einen Verweis auf eine Adresse zurückgeben, die gleich Null ist, z. B. die zurückgegeben System.Runtime.CompilerServices.Unsafe.NullRef<T>() wird, wenn keine Daten zum Anheften vorhanden sind.
  • Ein simple_name oder member_access , das auf ein Pufferelement mit fester Größe einer verschiebebaren Variable verweist, vorausgesetzt, der Typ des Pufferelements fester Größe ist implizit in den in der fixed Anweisung angegebenen Zeigertyp konvertierbar. In diesem Fall berechnet der Initialisierer einen Zeiger auf das erste Element des Puffers mit fester Größe (§23.8.3), und der Puffer mit fester Größe bleibt garantiert bei einer festen Adresse für die Dauer der fixed Anweisung.

Für jede von einem fixed_pointer_initializer berechnete Adresse stellt die fixed Anweisung sicher, dass die variable, auf die von der Adresse verwiesen wird, nicht der Verlagerung oder Entsorgung durch den Garbage Collector für die Dauer der fixed Anweisung unterliegt.

Beispiel: Wenn die von einem fixed_pointer_initializer berechnete Adresse auf ein Feld eines Objekts oder ein Element einer Arrayinstanz verweist, garantiert die feste Anweisung, dass die enthaltende Objektinstanz während der Lebensdauer der Anweisung nicht verschoben oder verworfen wird. Endbeispiel

Es liegt in der Verantwortung des Programmierers, sicherzustellen, dass Zeiger, die mit festen Anweisungen erstellt wurden, nicht über die Ausführung dieser Anweisungen hinausgehen.

Beispiel: Wenn Zeiger, die von fixed Anweisungen erstellt wurden, an externe APIs übergeben werden, liegt es in der Verantwortung des Programmierers, sicherzustellen, dass die APIs keinen Speicher dieser Zeiger beibehalten. Endbeispiel

Feste Objekte können zu einer Fragmentierung des Heaps führen (da sie nicht verschoben werden können). Aus diesem Grund sollten Objekte nur dann festgelegt werden, wenn dies unbedingt erforderlich ist und dann nur für den kürzesten Zeitraum möglich ist.

Beispiel: Das Beispiel

class Test
{
    static int x;
    int y;

    unsafe static void F(int* p)
    {
        *p = 1;
    }

    static void Main()
    {
        Test t = new Test();
        int[] a = new int[10];
        unsafe
        {
            fixed (int* p = &x) F(p);
            fixed (int* p = &t.y) F(p);
            fixed (int* p = &a[0]) F(p);
            fixed (int* p = a) F(p);
        }
    }
}

veranschaulicht mehrere Verwendungen der fixed Anweisung. Die erste Anweisung behebt und ruft die Adresse eines statischen Felds ab, die zweite Anweisung behebt und ruft die Adresse eines Instanzfelds ab, und die dritte Anweisung behebt und ruft die Adresse eines Arrayelements ab. In jedem Fall wäre es ein Fehler gewesen, den regulären & Operator zu verwenden, da die Variablen alle als verschiebebare Variablen klassifiziert werden.

Die dritte und vierte Anweisung im obigen fixed Beispiel erzeugen identische Ergebnisse. Im Allgemeinen ist die Angabe a[0] in einer fixed Anweisung für eine Arrayinstanz aidentisch mit der angabe einfach.a

Endbeispiel

In einem unsicheren Kontext werden Arrayelemente eindimensionaler Arrays in zunehmender Indexreihenfolge gespeichert, beginnend mit Index 0 und Ende mit Index Length – 1. Bei mehrdimensionalen Arrays werden Arrayelemente so gespeichert, dass die Indizes der äußerst rechten Dimension zuerst, dann die nächste linke Dimension usw. erhöht werden.

Innerhalb einer fixed Anweisung, die einen Zeiger p auf eine Arrayinstanz aabruft, reichen die Zeigerwerte von p bis hin zur p + a.Length - 1 Darstellung von Adressen der Elemente im Array. Ebenso sind die Variablen von p[0] bis hin zur p[a.Length - 1] Darstellung der tatsächlichen Arrayelemente. Angesichts der Art und Weise, in der Arrays gespeichert werden, kann ein Array jeder Dimension so behandelt werden, als ob es linear wäre.

Beispiel:

class Test
{
    static void Main()
    {
        int[,,] a = new int[2,3,4];
        unsafe
        {
            fixed (int* p = a)
            {
                for (int i = 0; i < a.Length; ++i) // treat as linear
                {
                    p[i] = i;
                }
            }
        }
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 3; ++j)
            {
                for (int k = 0; k < 4; ++k)
                {
                    Console.Write($"[{i},{j},{k}] = {a[i,j,k],2} ");
                }
                Console.WriteLine();
            }
        }
    }
}

die Ausgabe erzeugt:

[0,0,0] =  0 [0,0,1] =  1 [0,0,2] =  2 [0,0,3] =  3
[0,1,0] =  4 [0,1,1] =  5 [0,1,2] =  6 [0,1,3] =  7
[0,2,0] =  8 [0,2,1] =  9 [0,2,2] = 10 [0,2,3] = 11
[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15
[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19
[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23

Endbeispiel

Beispiel: Im folgenden Code

class Test
{
    unsafe static void Fill(int* p, int count, int value)
    {
        for (; count != 0; count--)
        {
            *p++ = value;
        }
    }

    static void Main()
    {
        int[] a = new int[100];
        unsafe
        {
            fixed (int* p = a) Fill(p, 100, -1);
        }
    }
}

Eine fixed Anweisung wird verwendet, um ein Array zu korrigieren, damit seine Adresse an eine Methode übergeben werden kann, die einen Zeiger verwendet.

Endbeispiel

Ein char* Wert, der durch Das Korrigieren einer Zeichenfolgeninstanz erzeugt wird, verweist immer auf eine mit Null beendete Zeichenfolge. In einer festen Anweisung, die einen Zeiger p auf eine Zeichenfolgeninstanz sabruft, zeigen die Zeigerwerte von p bis hin zur p + s.Length ‑ 1 Darstellung von Adressen der Zeichen in der Zeichenfolge und der Zeigerwert p + s.Length immer auf ein Nullzeichen (das Zeichen mit dem Wert '\0').

Beispiel:

class Test
{
    static string name = "xx";

    unsafe static void F(char* p)
    {
        for (int i = 0; p[i] != '\0'; ++i)
        {
            System.Console.WriteLine(p[i]);
        }
    }

    static void Main()
    {
        unsafe
        {
            fixed (char* p = name) F(p);
            fixed (char* p = "xx") F(p);
        }
    }
}

Endbeispiel

Beispiel: Der folgende Code zeigt eine fixed_pointer_initializer mit einem anderen Ausdruck als array_type oder string:

public class C
{
    private int _value;
    public C(int value) => _value = value;
    public ref int GetPinnableReference() => ref _value;
}

public class Test
{
    unsafe private static void Main()
    {
        C c = new C(10);
        fixed (int* p = c)
        {
            // ...
        }
    }
}

Der Typ C verfügt über eine barrierefreie GetPinnableReference Methode mit der richtigen Signatur. In der fixed Anweisung wird die ref int von dieser Methode zurückgegebene Methode verwendet, wenn sie aufgerufen c wird, um den int* Zeiger pzu initialisieren. Endbeispiel

Das Ändern von Objekten mit verwaltetem Typ über feste Zeiger kann zu einem nicht definierten Verhalten führen.

Hinweis: Da z. B. Zeichenfolgen unveränderlich sind, liegt es in der Verantwortung des Programmierers, sicherzustellen, dass die Zeichen, auf die durch einen Zeiger auf eine feste Zeichenfolge verwiesen wird, nicht geändert werden. Endnote

Hinweis: Die automatische NULL-Beendigung von Zeichenfolgen ist besonders praktisch beim Aufrufen externer APIs, die "C-style"-Zeichenfolgen erwarten. Beachten Sie jedoch, dass eine Zeichenfolgeninstanz NULL-Zeichen enthalten darf. Wenn solche NULL-Zeichen vorhanden sind, wird die Zeichenfolge abgeschnitten angezeigt, wenn sie als NULL-beendet char*behandelt wird. Endnote

23.8 Puffer mit fester Größe

23.8.1 Allgemein

Puffer mit fester Größe werden verwendet, um "C-style"-In-Line-Arrays als Elemente von Strukturen zu deklarieren und sind in erster Linie für die Interfacing mit nicht verwalteten APIs nützlich.

23.8.2 Pufferdeklarationen fester Größe

Ein Puffer mit fester Größe ist ein Element, das Speicher für einen Variablenpuffer mit fester Länge eines bestimmten Typs darstellt. Eine Pufferdeklaration mit fester Größe führt einen oder mehrere Puffer mit fester Größe eines bestimmten Elementtyps ein.

Hinweis: Wie ein Array kann ein Puffer mit fester Größe als enthaltende Elemente betrachtet werden. Der Ausdruckselementtyp, der für ein Array definiert ist, wird auch mit einem Puffer mit fester Größe verwendet. Endnote

Puffer mit fester Größe sind nur in Strukturdeklarationen zulässig und dürfen nur in unsicheren Kontexten (§23.2) auftreten.

fixed_size_buffer_declaration
    : attributes? fixed_size_buffer_modifier* 'fixed' buffer_element_type
      fixed_size_buffer_declarators ';'
    ;

fixed_size_buffer_modifier
    : 'new'
    | 'public'
    | 'internal'
    | 'private'
    | 'unsafe'
    ;

buffer_element_type
    : type
    ;

fixed_size_buffer_declarators
    : fixed_size_buffer_declarator (',' fixed_size_buffer_declarator)*
    ;

fixed_size_buffer_declarator
    : identifier '[' constant_expression ']'
    ;

Eine Pufferdeklaration mit fester Größe kann einen Satz von Attributen (§22), einen new Modifizierer (§15.3.5), Barrierefreiheitsmodifizierer enthalten, die den deklarierten Zugriffsberechtigungen entsprechen, die für Strukturmember (§16.4.3) und einen unsafe Modifizierer (§23.2) zulässig sind. Die Attribute und Modifizierer gelten für alle Elemente, die von der Pufferdeklaration mit fester Größe deklariert wurden. Es handelt sich um einen Fehler für denselben Modifizierer, der mehrmals in einer Pufferdeklaration mit fester Größe angezeigt wird.

Eine Pufferdeklaration mit fester Größe darf den static Modifizierer nicht einschließen.

Der Pufferelementtyp einer Pufferdeklaration mit fester Größe gibt den Elementtyp der Puffer an, die durch die Deklaration eingeführt wurden. Der Pufferelementtyp muss einen der vordefinierten Typen sbyte, , byte, , short, ushortulonguintintcharfloatlongdoubleoder .bool

Auf den Pufferelementtyp folgt eine Liste der Pufferdeklaratoren mit fester Größe, von denen jedes ein neues Element einführt. Ein Pufferdeklarator mit fester Größe besteht aus einem Bezeichner, der das Element benennt, gefolgt von einem konstanten Ausdruck, der in [ und ] Token eingeschlossen ist. Der Konstantenausdruck gibt die Anzahl der Elemente im Element an, das von diesem Deklarator für Puffer mit fester Größe eingeführt wurde. Der Typ des Konstantenausdrucks muss implizit in typkonvertierbar intsein, und der Wert muss eine nicht null positive ganze Zahl sein.

Die Elemente eines Puffers mit fester Größe müssen sequenziell im Arbeitsspeicher angeordnet werden.

Eine Pufferdeklaration mit fester Größe, die mehrere Puffer mit fester Größe deklariert, entspricht mehreren Deklarationen einer einzelnen Pufferdeklaration mit fester Größe mit denselben Attributen und Elementtypen.

Beispiel:

unsafe struct A
{
    public fixed int x[5], y[10], z[100];
}

für die folgende Syntax:

unsafe struct A
{
    public fixed int x[5];
    public fixed int y[10];
    public fixed int z[100];
}

Endbeispiel

23.8.3 Puffer mit fester Größe in Ausdrücken

Die Elementsuche (§12.5) eines Pufferelements mit fester Größe wird genau wie die Elementsuche eines Felds fortgesetzt.

Auf einen Puffer mit fester Größe kann in einem Ausdruck mithilfe eines simple_name (§12.8.4), eines member_access (§12.8.7) oder eines element_access (§12.8.12) verwiesen werden.

Wenn auf ein Puffermememm mit fester Größe als einfacher Name verwiesen wird, entspricht der Effekt dem Memberzugriff des Formulars this.I, wobei I es sich um das Pufferelement mit fester Größe handelt.

In einem Memberzugriff des Formulars E.I , bei dem E. es sich um den impliziten this.Elementtyp handelt, wenn E es sich um einen Strukturtyp handelt und eine Elementsuche I dieses Strukturtyps ein Element mit fester Größe identifiziert, wird dann E.I ausgewertet und wie folgt klassifiziert:

  • Wenn der Ausdruck E.I nicht in einem unsicheren Kontext auftritt, tritt ein Kompilierungszeitfehler auf.
  • Wenn E sie als Wert klassifiziert wird, tritt ein Kompilierungszeitfehler auf.
  • Andernfalls, wenn E es sich um eine verschiebebare Variable (§23.4) handelt, dann:
    • Wenn der Ausdruck E.I ein fixed_pointer_initializer (§23.7) ist, ist das Ergebnis des Ausdrucks ein Zeiger auf das erste Element des Pufferelements I mit fester Größe in E.
    • Andernfalls ist der Ausdruck ein primary_no_array_creation_expression (§12.8.12.1) innerhalb eines element_access (§12.8.12) des FormularsE.I[J], dann ist das Ergebnis E.I eines Zeigers, Pauf das erste Element des Pufferelements I fester Größe in E, und die eingeschlossene element_access wird dann als pointer_element_access (§23.6.4) P[J]ausgewertet. E.I
    • Andernfalls tritt ein Kompilierungsfehler auf.
  • E Andernfalls wird auf eine feste Variable verwiesen, und das Ergebnis des Ausdrucks ist ein Zeiger auf das erste Element des Pufferelements I mit fester Größe in E. Das Ergebnis ist vom Typ S*, wobei S der Elementtyp ist Iund als Wert klassifiziert wird.

Auf die nachfolgenden Elemente des Puffers mit fester Größe kann über Zeigervorgänge aus dem ersten Element zugegriffen werden. Im Gegensatz zum Zugriff auf Arrays ist der Zugriff auf die Elemente eines Puffers mit fester Größe ein unsicherer Vorgang und ist nicht bereichsgeprüft.

Beispiel: Im Folgenden wird eine Struktur mit einem Puffermememm mit fester Größe deklariert und verwendet.

unsafe struct Font
{
    public int size;
    public fixed char name[32];
}

class Test
{
    unsafe static void PutString(string s, char* buffer, int bufSize)
    {
        int len = s.Length;
        if (len > bufSize)
        {
            len = bufSize;
        }
        for (int i = 0; i < len; i++)
        {
            buffer[i] = s[i];
        }
        for (int i = len; i < bufSize; i++)
        {
            buffer[i] = (char)0;
        }
    }

    unsafe static void Main()
    {
        Font f;
        f.size = 10;
        PutString("Times New Roman", f.name, 32);
    }
}

Endbeispiel

23.8.4 Eindeutige Zuordnungsprüfung

Puffer mit fester Größe unterliegen nicht der endgültigen Zuordnungsprüfung (§9.4), und Puffermember mit fester Größe werden für zwecke der endgültigen Zuordnungsprüfung von Strukturtypvariablen ignoriert.

Wenn die äußerste Strukturvariable eines Pufferelements mit fester Größe eine statische Variable, eine Instanzvariable einer Klasseninstanz oder ein Arrayelement ist, werden die Elemente des Puffers mit fester Größe automatisch mit ihren Standardwerten initialisiert (§9.3). In allen anderen Fällen ist der anfängliche Inhalt eines Puffers mit fester Größe nicht definiert.

23.9 Stapelzuweisung

Allgemeine Informationen zum Betreiber stackallocfinden Sie unter §12.8.22. Hier wird die Fähigkeit dieses Operators zum Ergebnis eines Zeigers erläutert.

In einem unsicheren Kontext, wenn ein stackalloc_expression (§12.8.22) als Initialisierungsausdruck eines local_variable_declaration (§13.6.2) auftritt, wobei die local_variable_type entweder ein Zeigertyp (§23.3) oder abgeleitet (var) ist, ist das Ergebnis der stackalloc_expression ein Zeiger vom TypT *, der anfang des zugewiesenen Blocks sein soll, wo T befindet sich die unmanaged_type der stackalloc_expression.

In allen anderen Aspekten folgen die Semantik von local_variable_declaration s (§13.6.2) und stackalloc_expression(§12.8.22) in unsicheren Kontexten denen, die für sichere Kontextedefiniert sind.

Beispiel:

unsafe 
{
    // Memory uninitialized
    int* p1 = stackalloc int[3];
    // Memory initialized
    int* p2 = stackalloc int[3] { -10, -15, -30 };
    // Type int is inferred
    int* p3 = stackalloc[] { 11, 12, 13 };
    // Can't infer context, so pointer result assumed
    var p4 = stackalloc[] { 11, 12, 13 };
    // Error; no conversion exists
    long* p5 = stackalloc[] { 11, 12, 13 };
    // Converts 11 and 13, and returns long*
    long* p6 = stackalloc[] { 11, 12L, 13 };
    // Converts all and returns long*
    long* p7 = stackalloc long[] { 11, 12, 13 };
}

Endbeispiel

Im Gegensatz zum Zugriff auf Arrays oder stackalloc'ed-Blöcke des Span<T> Typs ist der Zugriff auf die Elemente eines stackalloced-Zeigertyps ein unsicherer Vorgang und ist nicht bereichsgecheckt.

Beispiel: Im folgenden Code

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        int n = value >= 0 ? value : -value;
        unsafe
        {
            char* buffer = stackalloc char[16];
            char* p = buffer + 16;
            do
            {
                *--p = (char)(n % 10 + '0');
                n /= 10;
            } while (n != 0);
            if (value < 0)
            {
                *--p = '-';
            }
            return new string(p, 0, (int)(buffer + 16 - p));
        }
    }

    static void Main()
    {
        Console.WriteLine(IntToString(12345));
        Console.WriteLine(IntToString(-999));
    }
}

Ein stackalloc Ausdruck wird in der IntToString Methode verwendet, um einen Puffer von 16 Zeichen im Stapel zuzuweisen. Der Puffer wird automatisch verworfen, wenn die Methode zurückgegeben wird.

Beachten Sie jedoch, dass dies IntToString im abgesicherten Modus neu geschrieben werden kann; das heißt, ohne Zeiger wie folgt zu verwenden:

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        int n = value >= 0 ? value : -value;
        Span<char> buffer = stackalloc char[16];
        int idx = 16;
        do
        {
            buffer[--idx] = (char)(n % 10 + '0');
            n /= 10;
        } while (n != 0);
        if (value < 0)
        {
            buffer[--idx] = '-';
        }
        return buffer.Slice(idx).ToString();
    }
}

Endbeispiel

Ende des bedingt normativen Texts.