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
unsafe
gekennzeichnet 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, dieLeft
FelderRight
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
MethodeA
führt einfach dazu, dass der textbezogene UmfangF
zu einem unsicheren Kontext wird, in dem die unsicheren Features der Sprache verwendet werden können. In der Außerkraftsetzung vonF
"inB
" muss derunsafe
Modifizierer nicht erneut angegeben werden, es sei denn, dieF
MethodeB
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
F
die 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 FallA
ist, oder durch Einschließen einesunsafe
Modifizierers in die Methodendeklaration, wie dies derB
Fall 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 void
geschrieben, 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 T
dar. Der Zeigerdereferenzierungsoperator *
(§23.6.2) kann für den Zugriff auf diese Variable verwendet werden.
Beispiel: Bei einer Variablen
P
vom Typint*
gibt der Ausdruck*P
die Variable an, dieint
an der adresseP
enthalten 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
int
n, der Elementanzahl dieser Sequenz und ein andererint
Wert gegeben wird, gibt die folgende Methode die Adresse dieses Werts in dieser Sequenz zurück, wenn eine Übereinstimmung auftritt; andernfalls wird folgendes zurückgegebennull
: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:
- Eine Variable, die sich aus einer simple_name (§12.8.4) bezieht, die auf eine lokale Variable, einen Wertparameter oder ein Parameterarray verweist, es sei denn, die Variable wird von einer anonymen Funktion (§12.19.6.2) erfasst.
- Eine Variable, die sich aus einer member_access (§12.8.7) des Formulars
V.I
ergibt, wobeiV
es sich um eine feste Variable eines struct_type handelt. - Eine Variable, die sich aus einer pointer_indirection_expression (§23.6.2) des Formulars
*P
, einer pointer_member_access (§23.6.3) des FormularsP->I
oder einer pointer_element_access (§23.6.4) des FormularsP[E]
ergibt.
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
,int
ushort
, ,uint
, oderulong
long
zu einer beliebigen pointer_type. - Von jeder pointer_type bis zu
sbyte
,byte
,short
,ushort
,int
, , ,uint
, , oderulong
long
.
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 B
richtig 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 C
eines 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 byte
konvertiert 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 oderulong
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 überSystem.Array
oder die schnittstellen zuzugreifen, die es implementiert, kann jedoch zur Laufzeit zu einer Ausnahme führen, da Zeigertypen nicht zu konvertierenobject
sind. - Die impliziten und expliziten Verweiskonvertierungen (§10.2.8, §10.3.5) von einem eindimensionalen Arraytyp
S[]
inSystem.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 ArraytypT[]
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
... in
sind 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) V
in gibt, wird ein Fehler erzeugt, und es werden keine weiteren Schritte ausgeführt. Wenn x
der Wert null
vorhanden 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->I
P
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 VorgangP->I
genau gleichbedeutend(*P).I
ist, könnte dieMain
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
, , long
oder 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 VorgangP[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 E
wird. 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 Initialisierenp
verwendet wird. Die Zuweisung, die*p
wirksam initialisierti
wird, 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
, , long
oder 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 P
Adresse 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 TypsT
, vorausgesetzt, der TypT*
ist implizit in den in derfixed
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 TypT*
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 derfixed
Anweisung. Wenn der Arrayausdruck null-Elemente aufweistnull
oder wenn das Array Nullelemente enthält, berechnet der Initialisierer eine Adresse, die gleich Null ist. - Ein Ausdruck vom Typ
string
, vorausgesetzt, der Typchar*
ist implizit in den in derfixed
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 derfixed
Anweisung. Das Verhalten derfixed
Anweisung ist implementierungsdefiniert, wenn der Zeichenfolgenausdruck lautetnull
. - Ein anderer Ausdruck als array_type oder , vorausgesetzt, es gibt eine barrierefreie Methode oder barrierefreie Erweiterungsmethode, die der Signatur
ref [readonly] T GetPinnableReference()
entspricht, wobeiT
ein unmanaged_type ist undT*
implizit in den in derfixed
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 derfixed
Anweisung. EineGetPinnableReference()
Methode kann von derfixed
Anweisung verwendet werden, wenn die Überladungsauflösung (§12.6.4) genau ein Funktionselement erzeugt, und dieses Funktionselement erfüllt die vorherigen Bedingungen. DieGetPinnableReference
Methode sollte einen Verweis auf eine Adresse zurückgeben, die gleich Null ist, z. B. die zurückgegebenSystem.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 derfixed
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 Angabea[0]
in einerfixed
Anweisung für eine Arrayinstanza
identisch 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 a
abruft, 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 s
abruft, 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 barrierefreieGetPinnableReference
Methode mit der richtigen Signatur. In derfixed
Anweisung wird dieref int
von dieser Methode zurückgegebene Methode verwendet, wenn sie aufgerufenc
wird, um denint*
Zeigerp
zu 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
, ushort
ulong
uint
int
char
float
long
double
oder .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 int
sein, 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 PufferelementsI
mit fester Größe inE
. - Andernfalls ist der Ausdruck ein primary_no_array_creation_expression (§12.8.12.1) innerhalb eines element_access (§12.8.12) des Formulars
E.I[J]
, dann ist das ErgebnisE.I
eines Zeigers,P
auf das erste Element des PufferelementsI
fester Größe inE
, und die eingeschlossene element_access wird dann als pointer_element_access (§23.6.4)P[J]
ausgewertet.E.I
- Andernfalls tritt ein Kompilierungsfehler auf.
- Wenn der Ausdruck
E
Andernfalls wird auf eine feste Variable verwiesen, und das Ergebnis des Ausdrucks ist ein Zeiger auf das erste Element des PufferelementsI
mit fester Größe inE
. Das Ergebnis ist vom TypS*
, wobei S der Elementtyp istI
und 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 stackalloc
finden 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 stackalloc
ed-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 derIntToString
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.
ECMA C# draft specification