Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
23.1 Algemeen
Een implementatie die geen onveilige code ondersteunt, is vereist om een diagnose te stellen van het gebruik van de syntactische regels die in deze component zijn gedefinieerd.
De rest van deze component, met inbegrip van alle subclauses, is voorwaardelijk normatief.
Opmerking: De C#-kerntaal, zoals gedefinieerd in de voorgaande componenten, verschilt met name van C en C++ in het weglaten van aanwijzers als gegevenstype. In plaats daarvan biedt C# verwijzingen en de mogelijkheid om objecten te maken die worden beheerd door een garbagecollector. Dit ontwerp, in combinatie met andere functies, maakt C# een veel veiligere taal dan C of C++. In de C#-kerntaal is het eenvoudigweg niet mogelijk om een niet-geïnitialiseerde variabele, een 'zwevende' aanwijzer of een expressie te hebben waarmee een matrix buiten de grenzen wordt geïndexeerd. Hele categorieën bugs die regelmatig C- en C++-programma's pesten, worden dus geëlimineerd.
Hoewel vrijwel elke aanwijzertypeconstructie in C of C++ een tegenhanger van het referentietype in C# heeft, zijn er echter situaties waarin toegang tot aanwijzertypen noodzakelijk wordt. Bijvoorbeeld, als u verbinding maakt met het onderliggende besturingssysteem, toegang krijgt tot een apparaat dat in het geheugen is geplaatst of een tijdkritisch algoritme implementeert, is dit mogelijk of praktisch niet zonder toegang tot pointers. Om deze behoefte te verhelpen, biedt C# de mogelijkheid om onveilige code te schrijven.
In onveilige code is het mogelijk om aanwijzers te declareren en te gebruiken, conversies tussen aanwijzers en integrale typen uit te voeren, om het adres van variabelen, enzovoort, op te nemen. In zekere zin is het schrijven van onveilige code vergelijkbaar met het schrijven van C-code in een C#-programma.
Onveilige code is in feite een 'veilige' functie vanuit het perspectief van zowel ontwikkelaars als gebruikers. Onveilige code moet duidelijk worden gemarkeerd met de modifier
unsafe
, zodat ontwikkelaars niet per ongeluk onveilige functies kunnen gebruiken en de uitvoeringsengine werkt om ervoor te zorgen dat onveilige code niet kan worden uitgevoerd in een niet-vertrouwde omgeving.eindnotitie
23.2 Onveilige contexten
De onveilige functies van C# zijn alleen beschikbaar in onveilige contexten. Een onveilige context wordt geïntroduceerd door een unsafe
wijziging op te nemen in de declaratie van een type, lid of lokale functie, of door een unsafe_statement te gebruiken:
- Een declaratie van een klasse, struct, interface of gemachtigde kan een
unsafe
wijzigingsfunctie bevatten. In dat geval wordt de volledige tekstuele omvang van die typedeclaratie (inclusief de hoofdtekst van de klasse, struct of interface) beschouwd als een onveilige context.Opmerking: Als de type_declaration gedeeltelijk is, is alleen dat deel een onveilige context. eindnotitie
- Een declaratie van een veld, methode, eigenschap, gebeurtenis, indexeerfunctie, operator, instantieconstructor, finalizer, statische constructor of lokale functie kan een
unsafe
wijzigingsfunctie bevatten. In dat geval wordt de volledige tekstuele omvang van die liddeclaratie beschouwd als een onveilige context. - Een unsafe_statement maakt het gebruik van een onveilige context binnen een blok mogelijk. De volledige tekstuele omvang van het gekoppelde blok wordt beschouwd als een onveilige context. Een lokale functie die binnen een onveilige context is gedeclareerd, is zelf onveilig.
De bijbehorende grammatica-extensies worden hieronder en in volgende subclauses weergegeven.
unsafe_modifier
: 'unsafe'
;
unsafe_statement
: 'unsafe' block
;
Voorbeeld: In de volgende code
public unsafe struct Node { public int Value; public Node* Left; public Node* Right; }
de
unsafe
wijziging die is opgegeven in de structdeclaratie zorgt ervoor dat de gehele tekstuele omvang van de structdeclaratie een onveilige context wordt. Het is dus mogelijk om deLeft
enRight
velden als van een pointertype te declareren. Het bovenstaande voorbeeld kan ook worden geschrevenpublic struct Node { public int Value; public unsafe Node* Left; public unsafe Node* Right; }
Hier zorgen de
unsafe
modifiers in de velddeclaraties ervoor dat deze declaraties als onveilige contexten worden beschouwd.eindvoorbeeld
Afgezien van het tot stand brengen van een onveilige context, waardoor het gebruik van aanwijzertypen is toegestaan, heeft de unsafe
wijzigingsfunctie geen effect op een type of lid.
Voorbeeld: In de volgende code
public class A { public unsafe virtual void F() { char* p; ... } } public class B : A { public override void F() { base.F(); ... } }
De onveilige modifier op de
F
methode binnenA
zorgt er eenvoudigweg voor dat het tekstuele bereik vanF
een onveilige context wordt waarin de onveilige functies van de taal kunnen worden gebruikt. Bij het overschrijven vanF
inB
hoeft u deunsafe
modifier niet opnieuw op te geven, tenzij deF
methode inB
zelf natuurlijk toegang nodig heeft tot onveilige functies.De situatie verschilt enigszins wanneer een aanwijzer deel uitmaakt van de handtekening van de methode
public unsafe class A { public virtual void F(char* p) {...} } public class B: A { public unsafe override void F(char* p) {...} }
F
Omdat de handtekening een aanwijzer bevat, kan deze alleen worden geschreven in een onveilige context. De onveilige context kan echter worden geïntroduceerd door de gehele klasse onveilig te maken, zoals het geval is inA
, of door eenunsafe
wijziging op te geven in de methodedeclaratie, zoals het geval is inB
.eindvoorbeeld
Wanneer de unsafe
wijzigingsfunctie wordt gebruikt voor een gedeeltelijke typedeclaratie (§15.2.7), wordt alleen dat bepaalde deel beschouwd als een onveilige context.
23.3 Aanwijzertypen
In een onveilige context kan een type (§8.1) een pointer_type zijn, evenals een value_type, een reference_type of een type_parameter. In een onveilige context kan een pointer_type ook het elementtype van een matrix zijn (§17). Een pointer_type kan ook worden gebruikt in een typeof-expressie (§12.8.18) buiten een onveilige context (omdat dergelijk gebruik niet onveilig is).
Een pointer_type wordt geschreven als unmanaged_type (§8.8) of het trefwoord void
, gevolgd door een *
token:
pointer_type
: value_type ('*')+
| 'void' ('*')+
;
Het type dat voorafgaat aan het *
in een aanwijzertype, wordt het verwijzingstype van het aanwijzertype genoemd. Het vertegenwoordigt het type variabele waarnaar een waarde van het aanwijzertype wijst.
Een pointer_type mag alleen worden gebruikt in een array_type in een onveilige context (§23.2). Een non_array_type is elk type dat geen array_type is.
In tegenstelling tot verwijzingen (waarden van referentietypen), worden aanwijzers niet bijgehouden door de garbagecollector—de garbagecollector heeft geen kennis van aanwijzers en de gegevens waarnaar ze verwijzen. Daarom mag een aanwijzer niet verwijzen naar een verwijzing of naar een struct die verwijzingen bevat, en het verwijzingstype van een aanwijzer een unmanaged_type. Aanwijzertypen zelf zijn niet-beheerde typen, dus een aanwijzertype kan worden gebruikt als het verwijzingstype voor een ander type aanwijzer.
De intuïtieve regel voor het combineren van aanwijzers en verwijzingen is dat verwijzingen van verwijzingen (objecten) aanwijzers mogen bevatten, maar verwijzingen van aanwijzers mogen geen verwijzingen bevatten.
Voorbeeld: Enkele voorbeelden van aanwijzertypen worden gegeven in de onderstaande tabel:
Voorbeeld Beschrijving byte*
Aanwijzer naar byte
char*
Aanwijzer naar char
int**
Aanwijzer naar aanwijzer naar int
int*[]
Eendimensionale matrix van aanwijzers naar int
void*
Aanwijzer naar onbekend type eindvoorbeeld
Voor een bepaalde uitvoering hebben alle aanwijzertypen dezelfde grootte en representatie.
Opmerking: In tegenstelling tot C en C++, wanneer meerdere aanwijzers in dezelfde declaratie worden gedeclareerd, wordt in C# de
*
tekst samen met het onderliggende type geschreven, niet als een voorvoegselpunctie voor elke aanwijzernaam. Voorbeeld:int* pi, pj; // NOT as int *pi, *pj;
eindnotitie
De waarde van een aanwijzer met een type T*
vertegenwoordigt het adres van een variabele van het type T
. De operator *
voor aanwijzer indirectie (§23.6.2) kan worden gebruikt voor toegang tot deze variabele.
Voorbeeld: Bij een variabele
P
van het typeint*
geeft de expressie*P
deint
variabele aan die is gevonden op het adres inP
. eindvoorbeeld
Net als een objectverwijzing kan een aanwijzer zijn null
. Het toepassen van de indirectieoperator op een null
-valued pointer resulteert in implementatiegedefinieerd gedrag (§23.6.2). Een aanwijzer met waarde null
wordt vertegenwoordigd door bits die allemaal nul zijn.
Het void*
type vertegenwoordigt een aanwijzer naar een onbekend type. Omdat het verwijzingstype onbekend is, kan de indirectieoperator niet worden toegepast op een aanwijzer van het type void*
, noch kan er rekenkundige bewerkingen worden uitgevoerd op een dergelijke aanwijzer. Een aanwijzer van het type void*
kan echter worden gecast naar elk ander type aanwijzer (en omgekeerd) en vergeleken met waarden van andere typen aanwijzers (§23.6.8).
Aanwijzertypen zijn een afzonderlijke categorie typen. In tegenstelling tot verwijzingstypen en waardetypen worden aanwijzertypen niet overgenomen van object
en bestaan er geen conversies tussen aanwijzertypen en object
. Met name boksen en uitpakken (§8.3.13) worden niet ondersteund voor aanwijzers. Conversies zijn echter toegestaan tussen verschillende typen aanwijzers en tussen aanwijzertypen en de integrale typen. Dit wordt beschreven in §23.5.
Een pointer_type kan niet worden gebruikt als een typeargument (§8.4) en typedeductie (§12.6.3) mislukt bij algemene methode-aanroepen die een typeargument hebben afgeleid als aanwijzertype.
Een pointer_type kan niet worden gebruikt als een type subexpressie van een dynamisch gebonden bewerking (§12.3.3).
Een pointer_type kan niet worden gebruikt als het type van de eerste parameter in een extensiemethode (§15.6.10).
Een pointer_type kan worden gebruikt als het type vluchtig veld (§15.5.4).
De dynamische wissing van een type E*
is het pointertype met als referenttype de dynamische wissing van E
.
Een expressie met een aanwijzertype kan niet worden gebruikt om de waarde in een member_declarator binnen een anonymous_object_creation_expression op te geven (§12.8.17.3).
De standaardwaarde (§9.3) voor elk type aanwijzer is null
.
Opmerking: Hoewel aanwijzers kunnen worden doorgegeven als by-reference-parameters, kan dit leiden tot niet-gedefinieerd gedrag, omdat de aanwijzer goed kan worden ingesteld op een lokale variabele die niet meer bestaat wanneer de aangeroepen methode retourneert, of het vaste object waarnaar wordt verwezen, niet meer is opgelost. Voorbeeld:
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 } } }
eindnotitie
Een methode kan een waarde van een bepaald type retourneren en dat type kan een aanwijzer zijn.
Voorbeeld: Wanneer een aanwijzer naar een aaneengesloten reeks
int
s, het aantal elementen van die reeks en een andereint
waarde wordt gegeven, retourneert de volgende methode het adres van die waarde in die volgorde, als er een overeenkomst plaatsvindt; anders wordt het volgende geretourneerdnull
: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; }
eindvoorbeeld
In een onveilige context zijn verschillende constructies beschikbaar voor gebruik op aanwijzers:
- De unaire
*
operator kan worden gebruikt om aanwijzer indirectie uit te voeren (§23.6.2). - De
->
operator kan worden gebruikt voor toegang tot een lid van een struct via een aanwijzer (§23.6.3). - De operator
[]
kan worden gebruikt om een aanwijzer te indexeren (§23.6.4). - De unaire
&
operator kan worden gebruikt om het adres van een variabele te verkrijgen (§23.6.5). - De operatoren
++
en--
kunnen worden gebruikt voor het verhogen en verlagen van aanwijzers (§23.6.6). - De binaire
+
en-
operatoren kunnen worden gebruikt voor het uitvoeren van aanwijzerberekeningen (§23.6.7). - De
==
,!=
,<
,>
,<=
en>=
operatoren kunnen worden gebruikt om aanwijzers te vergelijken (§23.6.8). - De
stackalloc
operator kan worden gebruikt om geheugen toe te wijzen vanuit de aanroepstack (§23.9). - De
fixed
instructie kan worden gebruikt om een variabele tijdelijk te herstellen, zodat het adres ervan kan worden verkregen (§23.7).
23.4 Vaste en verplaatsbare variabelen
Het adres van de operator (§23.6.5) en de fixed
instructie (§23.7) verdelen variabelen in twee categorieën: Vaste variabelen en verplaatsbare variabelen.
Vaste variabelen bevinden zich in opslaglocaties die niet worden beïnvloed door de werking van de garbagecollector. (Voorbeelden van vaste variabelen zijn lokale variabelen, waardeparameters en variabelen die zijn gemaakt door deductiepunten.) Aan de andere kant bevinden verplaatsbare variabelen zich in opslaglocaties die onderhevig zijn aan verplaatsing of verwijdering door de garbagecollector. (Voorbeelden van verplaatsbare variabelen zijn velden in objecten en elementen van matrices.)
De &
operator (§23.6.5) staat toe dat het adres van een vaste variabele zonder beperkingen kan worden verkregen. Omdat een verplaatsbare variabele echter onderhevig is aan verplaatsing of verwijdering door de garbagecollector, kan het adres van een verplaatsbare variabele alleen worden verkregen met behulp van een fixed statement
(§23.7) en dat adres blijft alleen geldig voor de duur van die fixed
verklaring.
In precieze termen is een vaste variabele een van de volgende:
- Een variabele die voortvloeit uit een simple_name (§12.8.4) die verwijst naar een lokale variabele, waardeparameter of parametermatrix, tenzij de variabele wordt vastgelegd door een anonieme functie (§12.19.6.2).
- Een variabele die voortvloeit uit een member_access (§12.8.7) van het formulier
V.I
, waarbijV
een vaste variabele van een struct_type is. - Een variabele die voortvloeit uit een pointer_indirection_expression (§23.6.2) van het formulier
*P
, een pointer_member_access (§23.6.3) van het formulierP->I
of een pointer_element_access (§23.6.4) van het formulierP[E]
.
Alle andere variabelen worden geclassificeerd als verplaatsbare variabelen.
Een statisch veld wordt geclassificeerd als een verplaatsbare variabele. Daarnaast wordt een by-reference-parameter geclassificeerd als een verplaatsbare variabele, zelfs als het argument dat voor de parameter is opgegeven een vaste variabele is. Ten slotte wordt een variabele die wordt geproduceerd door het uitstellen van een aanwijzer altijd geclassificeerd als een vaste variabele.
23.5 Aanwijzerconversies
23.5.1 Algemeen
In een onveilige context wordt de set beschikbare impliciete conversies (§10.2) uitgebreid met de volgende impliciete aanwijzerconversies:
- Van elke pointer_type tot het type
void*
. - Van de
null
letterlijke (§6.4.5.7) naar elk pointer_type.
Bovendien wordt in een onveilige context de set beschikbare expliciete conversies (§10.3) uitgebreid met de volgende expliciete aanwijzerconversies:
- Van elke pointer_type naar elke andere pointer_type.
- Van
sbyte
,byte
,short
,ushort
, ,int
,uint
oflong
ulong
naar een pointer_type. - Van elke pointer_type naar
sbyte
,byte
,short
,ushort
,int
,uint
,long
, ofulong
.
Ten slotte bevat de set impliciete standaardconversies (§10.4.2) in een onveilige context de volgende aanwijzerconversies:
- Van elke pointer_type tot het type
void*
. - Van de
null
letterlijke naar elke pointer_type.
Conversies tussen twee typen aanwijzers veranderen nooit de werkelijke waarde van de aanwijzer. Met andere woorden, een conversie van het ene type aanwijzer naar het andere heeft geen effect op het onderliggende adres dat door de aanwijzer wordt opgegeven.
Wanneer het ene type aanwijzer wordt geconverteerd naar een ander, is het gedrag niet gedefinieerd als de resulterende aanwijzer niet correct is uitgelijnd voor het type waarnaar verwezen wordt en het resultaat wordt gedereferenceerd. Over het algemeen is het concept 'correct uitgelijnd' transitief: als een aanwijzer naar type A
correct is uitgelijnd voor een aanwijzer naar type B
, die op zijn beurt correct is uitgelijnd voor een aanwijzer naar type C
, dan is een aanwijzer naar type A
correct uitgelijnd voor een aanwijzer naar type C
.
Voorbeeld: Houd rekening met het volgende geval waarin een variabele met één type wordt geopend via een aanwijzer naar een ander type:
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 }
eindvoorbeeld
Wanneer een aanwijzertype wordt geconverteerd naar een aanwijzer byte
, wijst het resultaat naar het laagste adres byte
van de variabele. Opeenvolgende stappen van het resultaat, tot de grootte van de variabele, geven aanwijzers naar de resterende bytes van die variabele.
Voorbeeld: Met de volgende methode wordt elk van de acht bytes in een
double
hexadecimale waarde weergegeven: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(); } } }
Natuurlijk is de geproduceerde uitvoer afhankelijk van de bytevolgorde (endianness). Een mogelijkheid is
" BA FF 51 A2 90 6C 24 45"
.eindvoorbeeld
Koppelingen tussen aanwijzers en gehele getallen worden door de implementatie bepaald.
Opmerking: op 32- en 64-bits CPU-architecturen met een lineaire adresruimte gedragen conversies van aanwijzers naar of van integrale typen zich doorgaans precies zoals conversies van
uint
respectievelijk waarden naar ofulong
van deze integrale typen. eindnotitie
23.5.2 Aanwijzermatrices
Matrices van aanwijzers kunnen worden samengesteld met behulp van array_creation_expression (§12.8.17.4) in een onveilige context. Alleen enkele conversies die van toepassing zijn op andere matrixtypen, zijn toegestaan op aanwijzermatrices:
- De impliciete verwijzingsconversie (§10.2.8) van elke array_type naar
System.Array
en de interfaces die ze implementeren, is ook van toepassing op wijzerarrays. Elke poging om toegang te krijgen tot de matrixelementen viaSystem.Array
of de interfaces die worden geïmplementeerd, kan echter leiden tot een uitzondering tijdens runtime, omdat aanwijzertypen niet kunnen worden omgezet inobject
. - De impliciete en expliciete verwijzingsconversies (§10.2.8, §10.3.5) van een enkeldimensionaal matrixtype
S[]
naarSystem.Collections.Generic.IList<T>
en de algemene basisinterfaces zijn nooit van toepassing op aanwijzermatrices. - De expliciete verwijzingsconversie (§10.3.5) van
System.Array
en de interfaces die het implementeert naar een array_type is van toepassing op pointer-arrays. - De expliciete verwijzingsconversies (§10.3.5) van
System.Collections.Generic.IList<S>
en de basisinterfaces naar een enkeldimensionaal matrixtypeT[]
zijn nooit van toepassing op aanwijzermatrices, omdat aanwijzertypen niet als typeargumenten kunnen worden gebruikt en er geen conversies zijn van aanwijzertypen naar niet-aanwijzertypen.
Deze beperkingen betekenen dat de uitbreiding van de foreach
instructie over reeksen die worden beschreven in §9.4.4.17 niet kan worden toegepast op aanwijzermatrices. In plaats daarvan een foreach
instructie van het formulier
foreach (V v in x)
embedded_statement
waarbij het type x
een matrixtype van het formulier T[,,...,]
is, n het aantal dimensies min 1 is en T
of V
een aanwijzertype is, als volgt wordt uitgebreid met geneste for-lussen:
{
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*
}
}
}
}
De variabelen a
, i0
, , i1
...
in
zijn niet zichtbaar voor of toegankelijk voor x
of de embedded_statement of een andere broncode van het programma. De variabele v
heeft het kenmerk Alleen-lezen in de ingesloten instructie. Als er geen expliciete conversie (§23.5) van T
(het elementtype) naar V
bestaat, wordt er een fout gegenereerd en worden er geen verdere stappen ondernomen. Als x
de waarde null
heeft, wordt er tijdens uitvoering een System.NullReferenceException
gegooid.
Opmerking: Hoewel aanwijzertypen niet zijn toegestaan als typeargumenten, kunnen aanwijzermatrices worden gebruikt als typeargumenten. eindnotitie
23.6 Aanwijzers in uitdrukkingen
23.6.1 Algemeen
In een onveilige context kan een expressie een resultaat opleveren van een aanwijzertype, maar buiten een onveilige context levert een expressie van een aanwijzertype een compilatiefout op. In precieze termen treedt er buiten een onveilige context een compilatiefout op als een simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) of element_access (§12.8.12) van een pointertype is.
In een onveilige context staan de producties primary_expression (§12.8) en unary_expression (§12.9) extra constructies toe, die worden beschreven in de volgende subclausules.
Opmerking: De prioriteit en associativiteit van de onveilige operators worden geïmpliceerd door de grammatica. eindnotitie
23.6.2 Indirecte aanwijzer
Een pointer_indirection_expression bestaat uit een sterretje (*
) gevolgd door een unary_expression.
pointer_indirection_expression
: '*' unary_expression
;
De unaire *
operator geeft aanwijzer indirectie aan en wordt gebruikt om de variabele te verkrijgen waarnaar een aanwijzer wijst. Het resultaat van het evalueren *P
, waarbij P
een expressie van een aanwijzertype T*
is, is een variabele van het type T
. Het is een compilatiefout om de unaire *
operator toe te passen op een expressie van het type void*
of op een expressie die niet van een type aanwijzer is.
Het effect van het toepassen van de unaire *
operator op een null
-valued pointer is door de implementatie gedefinieerd. In het bijzonder is er geen garantie dat deze bewerking een System.NullReferenceException
werpt.
Als er een ongeldige waarde is toegewezen aan de aanwijzer, is het gedrag van de unaire *
operator niet gedefinieerd.
Opmerking: Een van de ongeldige waarden voor het uitstellen van een aanwijzer door de unaire
*
operator is een adres dat onjuist is uitgelijnd voor het type waarnaar wordt verwezen (zie voorbeeld in §23.5) en het adres van een variabele na het einde van de levensduur.
Voor een definitieve toewijzingsanalyse wordt een variabele die wordt geproduceerd door het evalueren van een expressie van het formulier *P
beschouwd als in eerste instantie toegewezen (§9.4.2).
Toegang tot pointer-member 23.6.3.
Een pointer_member_access bestaat uit een primary_expression, gevolgd door een '->
'-token, gevolgd door een id en een optionele type_argument_list.
pointer_member_access
: primary_expression '->' identifier type_argument_list?
;
In een toegang tot een aanwijzerlid dat de vorm heeft van P->I
, moet P
een expressie zijn van een aanwijzertype, en I
moet een toegankelijk lid van het type aanduiden waarnaar P
verwijst.
De toegang tot een pointer lid in de vorm van P->I
wordt precies als (*P).I
geëvalueerd. Zie *
voor een beschrijving van de indirectieoperator van de aanwijzer (). Zie .
voor een beschrijving van de operator voor lidtoegang ().
Voorbeeld: In de volgende 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()); } } }
de
->
operator wordt gebruikt voor toegang tot velden en het aanroepen van een methode van een struct via een aanwijzer. Omdat de bewerkingP->I
precies gelijk is aan(*P).I
, kan deMain
methode even goed zijn geschreven:class Test { static void Main() { Point point; unsafe { Point* p = &point; (*p).x = 10; (*p).y = 20; Console.WriteLine((*p).ToString()); } } }
eindvoorbeeld
23.6.4 Toegang tot aanwijzerelementen
Een pointer_element_access bestaat uit een primary_expression gevolgd door een expressie tussen '[
' en '']
.
pointer_element_access
: primary_expression '[' expression ']'
;
Bij het erkennen van een primary_expression indien zowel de element_access als pointer_element_access (§23.6.4) alternatieven van toepassing zijn, wordt de laatste gekozen indien de ingesloten primary_expression van type aanwijzer is (§23.3).
In een toegang tot een aanwijzerelement van het formulier P[E]
P
moet een expressie zijn van een ander type aanwijzer dan void*
, en E
moet een expressie zijn die impliciet kan worden geconverteerd naar int
, uint
, long
of ulong
.
De toegang tot een aanwijzerelement van het formulier P[E]
wordt precies zo geëvalueerd als *(P + E)
. Zie *
voor een beschrijving van de indirectieoperator van de aanwijzer (). Zie +
voor een beschrijving van de operator voor aanwijzertoevoeging ().
Voorbeeld: In de volgende code
class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { p[i] = (char)i; } } } }
toegang tot een aanwijzerelement wordt gebruikt om de tekenbuffer in een
for
lus te initialiseren. Omdat de bewerkingP[E]
precies gelijk is aan*(P + E)
, kan het voorbeeld even goed zijn geschreven:class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { *(p + i) = (char)i; } } } }
eindvoorbeeld
De toegangsoperator voor aanwijzerelementen controleert niet op out-of-bounds-fouten en het gedrag bij het openen van een out-of-bounds-element is niet gedefinieerd.
Opmerking: dit is hetzelfde als C en C++. eindnotitie
23.6.5 Het adres van de operator
Een addressof_expression bestaat uit een ampersand (&
) gevolgd door een unary_expression.
addressof_expression
: '&' unary_expression
;
Uitgaande van een expressie E
die van een type T
is en wordt geclassificeerd als een vaste variabele (§23.4), berekent de constructie &E
het adres van de variabele die is opgegeven door E
. Het type van het resultaat is T*
en wordt geclassificeerd als een waarde. Er treedt een compileertijdfout op als E
deze niet is geclassificeerd als een variabele, als E
deze is geclassificeerd als een lokale variabele met het kenmerk Alleen-lezen of als E
deze een verplaatsbare variabele aangeeft. In het laatste geval kan een vaste instructie (§23.7) worden gebruikt om de variabele tijdelijk te "herstellen" voordat het adres wordt verkregen.
Opmerking: Zoals vermeld in §12.8.7, buiten een instantieconstructor of statische constructor voor een struct of klasse die een
readonly
veld definieert, wordt dat veld beschouwd als een waarde, niet als een variabele. Als zodanig kan het adres niet worden genomen. Op dezelfde manier kan het adres van een constante niet worden genomen. eindnotitie
De &
operator vereist niet dat het argument ervan zeker wordt toegewezen, maar na een &
bewerking wordt de variabele waarop de operator wordt toegepast, beschouwd als definitief toegewezen in het uitvoeringspad waarin de bewerking plaatsvindt. Het is de verantwoordelijkheid van de programmeur om ervoor te zorgen dat de juiste initialisatie van de variabele daadwerkelijk plaatsvindt in deze situatie.
Voorbeeld: In de volgende code
class Test { static void Main() { int i; unsafe { int* p = &i; *p = 123; } Console.WriteLine(i); } }
i
wordt beschouwd als definitief toegewezen na de&i
bewerking die wordt gebruikt om te initialiserenp
. De opdracht aan*p
initialiseerti
in feite, maar het opnemen van deze initialisatie is de verantwoordelijkheid van de programmeur en er zou geen compilatiefout optreden als de toewijzing werd verwijderd.eindvoorbeeld
Opmerking: De regels van definitieve toewijzing voor de
&
operator bestaan zodanig dat redundante initialisatie van lokale variabelen kan worden vermeden. Veel externe API's gebruiken bijvoorbeeld een aanwijzer naar een structuur die door de API wordt ingevuld. Aanroepen naar dergelijke API's geven doorgaans het adres van een lokale struct-variabele door. Zonder de regel is redundante initialisatie van de structvariabele vereist. eindnotitie
Opmerking: Wanneer een lokale variabele, waardeparameter of parametermatrix wordt vastgelegd door een anonieme functie (§12.8.24), wordt die lokale variabele, parameter of parametermatrix niet langer beschouwd als een vaste variabele (§23.7), maar wordt in plaats daarvan beschouwd als een verplaatsbare variabele. Het is dus een fout voor elke onveilige code om het adres te nemen van een lokale variabele, waardeparameter of parametermatrix die is vastgelegd door een anonieme functie. eindnotitie
23.6.6 Aanwijzer verhogen en verlagen
In een onveilige context kunnen de ++
en --
operators (§12.8.16 en §12.9.6) worden toegepast op aanwijzervariabelen van alle typen behalve void*
. Voor elk type aanwijzer T*
worden de volgende operators dus impliciet gedefinieerd:
T* operator ++(T* x);
T* operator --(T* x);
De operators produceren dezelfde resultaten als x+1
x-1
respectievelijk (§23.6.7). Met andere woorden, voor een aanwijzervariabele van het type T*
voegt de ++
operator toe sizeof(T)
aan het adres in de variabele en trekt de --
operator sizeof(T)
af van het adres dat in de variabele is opgenomen.
Als een aanwijzerverloop of -degradatiebewerking het domein van het type aanwijzer overloopt, wordt het resultaat door de implementatie gedefinieerd, maar worden er geen uitzonderingen geproduceerd.
23.6.7 Pointer-aritmetica
In een onveilige context kan de +
operator (§12.10.5) en -
operator (§12.10.6) worden toegepast op waarden van alle typen aanwijzers, behalve void*
. Voor elk type aanwijzer T*
worden de volgende operators dus impliciet gedefinieerd:
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);
Uitgaande van een expressie P
van een type aanwijzer T*
en een expressie N
van het type int
, uint
, long
of ulong
, de expressies P + N
en N + P
het berekenen van de waarde van de aanwijzer van het type T*
dat het resultaat is van het toevoegen N * sizeof(T)
aan het adres dat is opgegeven door P
. Op dezelfde manier berekent de expressie P – N
de aanwijzerwaarde van het type T*
die het gevolg is van het aftrekken van N * sizeof(T)
van het adres dat is opgegeven door P
.
Bij twee expressies, P
en Q
, van een aanwijzingstype T*
berekent de expressie P – Q
het verschil tussen de adressen die worden gegeven door P
en Q
en dan deelt het dat verschil door sizeof(T)
. Het type van het resultaat is altijd long
. In feite wordt P - Q
berekend als ((long)(P) - (long)(Q)) / sizeof(T)
.
Voorbeeld:
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 de uitvoer produceert:
p - q = -14 q - p = 14
eindvoorbeeld
Als een rekenkundige aanwijzerbewerking het domein van het type aanwijzer overloopt, wordt het resultaat afgekapt op een door de implementatie gedefinieerde manier, maar worden er geen uitzonderingen geproduceerd.
Aanwijzervergelijking 23.6.8
In een onveilige context kunnen de ==
, !=
, <
, >
, <=
en >=
operators (§12.12) worden toegepast op waarden van alle pointertypen. De vergelijkingsoperatoren voor aanwijzers zijn:
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);
Omdat er een impliciete conversie bestaat van elk type aanwijzer naar het void*
type, kunnen operanden van elk type aanwijzer worden vergeleken met behulp van deze operators. De vergelijkingsoperatoren vergelijken de adressen die door de twee operanden zijn opgegeven alsof ze niet-ondertekende gehele getallen zijn.
23.6.9 De grootte van de operator
Voor bepaalde vooraf gedefinieerde typen (§12.8.19) levert de sizeof
operator een constante int
waarde op. Voor alle andere typen is het resultaat van de operator door de sizeof
implementatie gedefinieerd en wordt het geclassificeerd als een waarde, niet als een constante.
De volgorde waarin leden in een struct zijn verpakt, is niet opgegeven.
Voor uitlijningsdoeleinden kunnen er aan het begin van een struct, binnen een struct en aan het einde van de struct, niet-benoemde opvulling aanwezig zijn. De inhoud van de bits die worden gebruikt als opvulling, zijn onbepaald.
Wanneer dit wordt toegepast op een operand met het type struct, is het resultaat het totale aantal bytes in een variabele van dat type, inclusief eventuele opvullingen.
23.7 De vaste verklaring
In een onveilige context maakt de productie van de embedded_statement (§13.1) een extra constructie mogelijk, de vaste instructie, die wordt gebruikt om een verplaatsbare variabele te "herstellen", zodat het adres constant blijft voor de duur van de instructie.
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
;
Elke fixed_pointer_declarator declareert een lokale variabele van de opgegeven pointer_type en initialiseert die lokale variabele met het adres dat wordt berekend door de bijbehorende fixed_pointer_initializer. Een lokale variabele die is gedeclareerd in een vaste instructie, is toegankelijk in fixed_pointer_initializers die zich rechts van de declaratie van die variabele voordoen en in de embedded_statement van de vaste instructie. Een lokale variabele die door een vast statement wordt gedeclareerd, wordt beschouwd als alleen-lezen. Er treedt een compilatiefout op als de ingesloten instructie deze lokale variabele probeert te wijzigen (via toewijzing of de ++
operatoren --
) of deze als referentie- of uitvoerparameter doorgeeft.
Het is een fout bij het gebruik van een vastgelegde lokale variabele (§12.19.6.2), waardeparameter of parametermatrix in een fixed_pointer_initializer. Een fixed_pointer_initializer kan een van de volgende zijn:
- Het token "
&
" gevolgd door een variable_reference (§9.5) naar een verplaatsbare variabele (§23.4) van een onbeheerd typeT
, mits het typeT*
impliciet wordt omgezet in het type aanwijzer dat in defixed
instructie is opgegeven. In dit geval berekent de initialisatiefunctie het adres van de opgegeven variabele en blijft de variabele gegarandeerd op een vast adres voor de duur van de vaste instructie. - Een expressie van een array_type met elementen van een onbeheerd type
T
, mits het typeT*
impliciet wordt omgezet in het type aanwijzer dat in de vaste instructie wordt opgegeven. In dit geval berekent de initialisatiefunctie het adres van het eerste element in de matrix en blijft de hele matrix gegarandeerd op een vast adres gedurende de duur van defixed
instructie. Als de matrixexpressienull
is of als de matrix nul elementen heeft, berekent de initialisatiefunctie een adres dat gelijk is aan nul. - Een expressie van het type
string
, mits het typechar*
impliciet wordt geconverteerd naar het type aanwijzer dat in defixed
instructie wordt opgegeven. In dit geval berekent de initialisatiefunctie het adres van het eerste teken in de tekenreeks en blijft de hele tekenreeks gegarandeerd op een vast adres gedurende de duur van defixed
instructie. Het gedrag van defixed
instructie is implementatiegedefinieerd als de tekenreeksexpressienull
is. - Een expressie van een ander type dan array_type of
string
, mits er een toegankelijke methode of een toegankelijke uitbreidingsmethode bestaat die overeenkomt met de handtekeningref [readonly] T GetPinnableReference()
, waarbijT
een unmanaged_type is enT*
impliciet kan worden omgezet in het type aanwijzer dat in defixed
instructie is opgegeven. In dit geval berekent de initialisatiefunctie het adres van de geretourneerde variabele en blijft die variabele gegarandeerd op een vast adres voor de duur van defixed
instructie. EenGetPinnableReference()
methode kan door defixed
instructie worden gebruikt wanneer overbelastingsresolutie (§12.6.4) precies één functielid produceert en dat functielid voldoet aan de voorgaande voorwaarden. DeGetPinnableReference
-methode moet een verwijzing retourneren naar een adres dat gelijk is aan nul, zoals geretourneerd doorSystem.Runtime.CompilerServices.Unsafe.NullRef<T>()
wanneer er geen gegevens zijn om vast te zetten. - Een simple_name of member_access die verwijst naar een bufferlid met een vaste grootte van een verplaatsbare variabele, mits het type bufferlid met vaste grootte impliciet wordt omgezet naar het type aanwijzer dat in de
fixed
instructie is opgegeven. In dit geval berekent de initialisatiefunctie een aanwijzer naar het eerste element van de buffer met vaste grootte (§23.8.3) en blijft de buffer met vaste grootte gegarandeerd op een vast adres voor de duur van defixed
instructie.
Voor elk adres dat wordt berekend door een fixed_pointer_initializer zorgt de fixed
instructie ervoor dat de variabele waarnaar wordt verwezen door het adres niet onderworpen is aan herlocatie of verwijdering door de garbagecollector voor de duur van de fixed
instructie.
Voorbeeld: Als het adres dat door een fixed_pointer_initializer wordt berekend, verwijst naar een veld van een object of een element van een matrixexemplaren, garandeert de vaste instructie dat het betreffende objectexemplaren niet wordt verplaatst of verwijderd tijdens de levensduur van de instructie. eindvoorbeeld
Het is de verantwoordelijkheid van de programmeur om ervoor te zorgen dat aanwijzers die zijn gemaakt door vaste instructies, niet buiten de uitvoering van deze instructies overleven.
Voorbeeld: Wanneer aanwijzers die zijn gemaakt door
fixed
instructies worden doorgegeven aan externe API's, is het de verantwoordelijkheid van de programmeur om ervoor te zorgen dat de API's geen geheugen van deze aanwijzers behouden. eindvoorbeeld
Vaste objecten kunnen fragmentatie van de heap veroorzaken (omdat ze niet kunnen worden verplaatst). Daarom moeten objecten alleen worden opgelost wanneer dat absoluut noodzakelijk is en vervolgens alleen voor de kortst mogelijke tijd.
Voorbeeld: Het voorbeeld
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); } } }
demonstreert verschillende toepassingen van de
fixed
statement. De eerste instructie fixeert en verkrijgt het adres van een statisch veld, de tweede instructie fixeert en verkrijgt het adres van een instantieveld, en de derde instructie fixeert en verkrijgt het adres van een arrayelement. In elk geval zou het een fout zijn geweest om de reguliere&
operator te gebruiken, omdat de variabelen allemaal zijn geclassificeerd als verplaatsbare variabelen.De derde en vierde
fixed
instructies in het bovenstaande voorbeeld produceren identieke resultaten. Over het algemeen, voor een matrixinstantiea
, is het opgeven vana[0]
in eenfixed
instructie hetzelfde als simpelwega
opgeven.eindvoorbeeld
In een onveilige context worden matrixelementen van eendimensionale matrices opgeslagen in toenemende indexvolgorde, beginnend met index 0
en eindigend met index Length – 1
. Voor multidimensionale matrices worden matrixelementen opgeslagen, zodat de indexen van de meest rechtse dimensie eerst worden verhoogd, vervolgens de volgende linkerdimensie, enzovoort aan de linkerkant.
Binnen een fixed
instructie die een aanwijzer p
naar een matrixexemplaar a
verkrijgt, zijn de aanwijzerwaarden variërend van p
tot p + a.Length - 1
adressen van de elementen in de matrix. Op dezelfde manier vertegenwoordigen de variabelen die variëren van p[0]
tot p[a.Length - 1]
de feitelijke array-elementen. Gezien de manier waarop matrices worden opgeslagen, kan een matrix van elke dimensie worden behandeld alsof deze lineair is.
Voorbeeld:
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 de uitvoer produceert:
[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
eindvoorbeeld
Voorbeeld: In de volgende 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); } } }
een
fixed
instructie wordt gebruikt om een matrix te herstellen, zodat het adres kan worden doorgegeven aan een methode die een aanwijzer gebruikt.eindvoorbeeld
Een char*
waarde die wordt geproduceerd door een tekenreeks-exemplaar te herstellen, verwijst altijd naar een nul-beëindigde tekenreeks. Binnen een vaste instructie die een aanwijzer p
naar een tekenreeksinstantie s
verkrijgt, verwijzen de aanwijzerwaarden van p
tot p + s.Length ‑ 1
naar adressen van de tekens in de tekenreeks, en de aanwijzerwaarde p + s.Length
verwijst altijd naar een null-teken (het teken met de waarde '\0').
Voorbeeld:
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); } } }
eindvoorbeeld
Voorbeeld: De volgende code toont een fixed_pointer_initializer met een andere expressie dan array_type of
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) { // ... } } }
Type
C
heeft een toegankelijkeGetPinnableReference
methode met de juiste handtekening. In defixed
instructie wordt deref int
die wordt geretourneerd van die methode wanneer deze wordt aangeroepen opc
, gebruikt om deint*
aanwijzerp
te initialiseren. eindvoorbeeld
Het wijzigen van objecten van het beheerde type via vaste aanwijzers kan leiden tot niet-gedefinieerd gedrag.
Opmerking: omdat tekenreeksen bijvoorbeeld onveranderbaar zijn, is het de verantwoordelijkheid van de programmeur om ervoor te zorgen dat de tekens waarnaar wordt verwezen door een aanwijzer naar een vaste tekenreeks niet worden gewijzigd. eindnotitie
Opmerking: de automatische null-beëindiging van tekenreeksen is met name handig bij het aanroepen van externe API's die 'C-style' tekenreeksen verwachten. Houd er echter rekening mee dat een tekenreeksexemplaar null-tekens kan bevatten. Als er dergelijke null-tekens aanwezig zijn, zal de tekenreeks afgekapt lijken wanneer deze wordt behandeld als een null-beëindigde
char*
. eindnotitie
23.8 Buffers met vaste grootte
23.8.1 Algemeen
Buffers met een vaste grootte worden gebruikt om 'C-style' in-line matrices als leden van structs te declareren en zijn vooral handig voor communicatie met niet-beheerde API's.
23.8.2 Bufferdeclaraties met vaste grootte
Een buffer met een vaste grootte is een lid dat de opslag vertegenwoordigt voor een buffer met vaste lengte van variabelen van een bepaald type. Een bufferdeclaratie met een vaste grootte introduceert een of meer buffers met vaste grootte van een bepaald elementtype.
Opmerking: Net als bij een matrix kan een buffer met vast formaat worden beschouwd als iets dat elementen bevat. Als zodanig wordt het elementtype zoals gedefinieerd voor een array eveneens gebruikt bij een buffer van vaste grootte. eindnotitie
Buffers met vaste grootte zijn alleen toegestaan in structdeclaraties en kunnen alleen optreden in onveilige contexten (§23.2).
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 ']'
;
Een declaratie van een buffer met een vaste grootte kan een set kenmerken (§22), een new
modifier (§15.3.5), toegankelijkheidsmodifiers bevatten die overeenkomen met een van de toegestane toegankelijkheden voor structleden (§16.4.3) en een unsafe
modifier (§23.2). De kenmerken en modifiers zijn van toepassing op alle leden die zijn gedeclareerd door de bufferdeclaratie van vaste grootte. Het is een fout dat dezelfde wijziging meerdere keren wordt weergegeven in een bufferdeclaratie met een vaste grootte.
Een bufferdeclaratie met een vaste grootte is niet toegestaan om de static
wijzigingsfunctie op te nemen.
Het type bufferelement van een bufferdeclaratie met vaste grootte geeft het elementtype van de buffers op die door de declaratie zijn ingevoerd. Het type bufferelement moet een van de vooraf-gedefinieerde typen sbyte
, byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, double
, of bool
zijn.
Het type bufferelement wordt gevolgd door een lijst met bufferdeclaraties van vaste grootte, die elk een nieuw lid introduceert. Een bufferdeclaratie met een vaste grootte bestaat uit een identifier die het lid benoemt, gevolgd door een constante uitdrukking tussen de [
en ]
tokens. De constante expressie geeft het aantal elementen in het lid aan dat door die bufferdeclaratie voor vaste grootte is geïntroduceerd. Het type van de constante expressie moet impliciet worden omgezet in type int
en de waarde moet een niet-nul positief geheel getal zijn.
De elementen van een buffer met vaste grootte moeten opeenvolgend in het geheugen worden ingedeeld.
Een bufferdeclaratie met vaste grootte die meerdere buffers met vaste grootte declareert, is gelijk aan meerdere declaraties van één bufferdeclaratie met één vaste grootte met dezelfde kenmerken en elementtypen.
Voorbeeld:
unsafe struct A { public fixed int x[5], y[10], z[100]; }
komt overeen met
unsafe struct A { public fixed int x[5]; public fixed int y[10]; public fixed int z[100]; }
eindvoorbeeld
23.8.3 Buffers met vaste grootte in expressies
Het opzoeken van leden (§12.5) van een bufferlid met vaste grootte gaat precies als het opzoeken van leden van een veld.
In een expressie kan naar een buffer met vaste grootte worden verwezen met behulp van een simple_name (§12.8.4), een member_access (§12.8.7) of een element_access (§12.8.12).
Wanneer naar een bufferlid met een vaste grootte wordt verwezen als een eenvoudige naam, is het effect hetzelfde als een lidtoegang tot het formulier this.I
, waarbij I
het bufferlid met vaste grootte is.
Bij een lidtoegang in de vorm van E.I
waar E.
het impliciete this.
kan zijn, als E
van een struct-type is en een opzoekactie naar het lid I
in dat structtype een lid met een vaste grootte identificeert, wordt E.I
als volgt geëvalueerd en geclassificeerd:
- Als de expressie
E.I
niet voorkomt in een onveilige context, treedt er een compilatietijdfout op. - Als
E
deze is geclassificeerd als een waarde, treedt er een compilatietijdfout op. -
E
Anders, als dit een verplaatsbare variabele is (§23.4), dan:- Als de expressie
E.I
een fixed_pointer_initializer is (§23.7), is het resultaat van de expressie een aanwijzer naar het eerste element van het bufferlidI
met een vaste grootte inE
. - Als de expressie
E.I
anders een primary_expression is (§12.8.12.1) binnen een element_access (§12.8.12) van het formulierE.I[J]
, wordt het resultaat vanE.I
een aanwijzer,P
naar het eerste element van het bufferlidI
E
met vaste grootte, en het insluiten van element_access vervolgens geëvalueerd als de pointer_element_access (§23.6.4).P[J]
- Anders treedt er een compilatietijdfout op.
- Als de expressie
- In dat geval, verwijst
E
naar een vaste variabele en is het resultaat van de expressie een aanwijzer naar het eerste element van het bufferlid met vaste grootteI
inE
. Het resultaat is van het typeS*
, waarbij S het elementtype isI
en wordt geclassificeerd als een waarde.
De volgende elementen van de buffer met vaste grootte kunnen worden geopend met behulp van aanwijzerbewerkingen vanaf het eerste element. In tegenstelling tot toegang tot matrices is de toegang tot de elementen van een buffer met een vaste grootte een onveilige bewerking en wordt het bereik niet gecontroleerd.
Voorbeeld: Het volgende declareert en gebruikt een struct met een bufferlid met een vaste grootte.
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); } }
eindvoorbeeld
23.8.4 Definitieve toewijzingscontrole
Buffers met vaste grootte zijn niet onderworpen aan definitieve toewijzingscontrole (§9.4) en bufferleden met vaste grootte worden genegeerd voor het controleren van de structtypevariabelen.
Wanneer de buitenste structvariabele van een bufferlid met vaste grootte een statische variabele, een exemplaarvariabele van een klasse-instantie, of een array-element is, worden de elementen van de buffer met vaste grootte automatisch geïnitialiseerd naar hun standaardwaarden (§9.3). In alle andere gevallen is de initiële inhoud van een buffer met een vaste grootte niet gedefinieerd.
23.9 Stacktoewijzing
Zie §12.8.22 voor algemene informatie over de operator stackalloc
. Hier wordt de mogelijkheid van die operator om een aanwijzer te geven besproken.
Wanneer een stackalloc_expression optreedt als de initialisatie-expressie van de local_variable_declaration (§13.6.2), waar het local_variable_type een pointer type is (§23.3) of afgeleid (var
), is het resultaat van de stackalloc_expression een aanwijzer van het type T*
, waar T
de unmanaged_type van de stackalloc_expressionis. In dit geval is het resultaat een aanwijzer naar het begin van het toegewezen blok.
Voorbeeld:
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 }; }
eindvoorbeeld
In tegenstelling tot toegang tot arrays of stackalloc
'ed-blokken van het type Span<T>
, is de toegang tot de elementen van een stackalloc
'ed-blok van het type aanwijzer een onveilige bewerking en wordt er niet op bereik gecontroleerd.
Voorbeeld: In de volgende 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)); } }
een
stackalloc
expressie wordt gebruikt in deIntToString
methode om een buffer van 16 tekens toe te wijzen aan de stack. De buffer wordt automatisch verwijderd wanneer de methode wordt geretourneerd.Houd er echter rekening mee dat dit
IntToString
kan worden herschreven in de veilige modus, dat wil gezegd, zonder aanwijzers te gebruiken, als volgt: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(); } }
eindvoorbeeld
Einde van voorwaardelijk normatieve tekst.
ECMA C# draft specification