Delen via


23 Onveilige code

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. Als u bijvoorbeeld verbinding maakt met het onderliggende besturingssysteem, toegang krijgt tot een apparaat dat is toegewezen aan het geheugen of het implementeren van een tijdkritisch algoritme, is het mogelijk of praktisch zonder toegang tot aanwijzers. 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 de Left velden Right van een type aanwijzer te declareren. Het bovenstaande voorbeeld kan ook worden geschreven

public 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();
        ...
    }
}

door de onveilige wijziging van de F methode wordt A de tekstuele mate van F een onveilige context waarin de onveilige functies van de taal kunnen worden gebruikt. In de onderdrukking van F in Bhoeft u de unsafe wijzigingsfunctie niet opnieuw op te geven, tenzij de F methode op B zichzelf 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) {...}
}

FOmdat 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 in A, of door een unsafe wijziging op te geven in de methodedeclaratie, zoals het geval is in B.

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 (zoals het gebruik is niet onveilig).

Een pointer_type wordt geschreven als een unmanaged_type (§8.8) of het trefwoord void, gevolgd door een * token:

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

Het type dat is opgegeven voor het * type aanwijzer, wordt het verwijzingstype van het type aanwijzer genoemd. Het vertegenwoordigt het type van de variabele waarnaar een waarde van het aanwijzertype punten.

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 garbagecollection 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) verwijzingen mogen bevatten, maar verwijzingen van verwijzingen 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 type int*geeft de expressie *P de int variabele aan die is gevonden op het adres in P. 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 alle bits-nul.

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 type aanwijzer met het referenttype van de dynamische wissing van E.

Een expressie met een type aanwijzer kan niet worden gebruikt om de waarde in een member_declarator binnen een anonymous_object_creation_expression op te geven (§12.8.17.7).

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 ints, het aantal elementen van die reeks en een andere int waarde wordt gegeven, retourneert de volgende methode het adres van die waarde in die volgorde, als er een overeenkomst plaatsvindt; anders wordt het volgende geretourneerd null:

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

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 ++ operators -- 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 ==operatoren , !=, <, ><=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:

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) tot elke 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 tot andere pointer_type.
  • Van sbyte, byte, short, ushort, , int, , uintof longnaar ulong een pointer_type.
  • Van elke pointer_type naar , , byte, short, ushort, int, , uint, of longulong.sbyte

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, als de resulterende aanwijzer niet correct is uitgelijnd voor het punt-naar-type, wordt het gedrag niet gedefinieerd als het resultaat wordt gededucteerd. Over het algemeen is het concept 'correct uitgelijnd' transitief: als een aanwijzer om te typen A juist is uitgelijnd voor een aanwijzer om te typen B, die op zijn beurt correct is uitgelijnd voor een aanwijzer om te typen C, wordt een A aanwijzer die moet worden getypt, correct uitgelijnd voor een aanwijzer om te typen 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 endianness. Een mogelijkheid is " BA FF 51 A2 90 6C 24 45".

eindvoorbeeld

Toewijzingen tussen aanwijzers en gehele getallen worden door de implementatie gedefinieerd.

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 of ulong van deze integrale typen. eindnotitie

23.5.2 Aanwijzermatrices

Matrices van aanwijzers kunnen worden samengesteld met behulp van array_creation_expression (§12.8.17.5) 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 alle array_type naar System.Array en de interfaces die worden geïmplementeerd, zijn ook van toepassing op aanwijzermatrices. Elke poging om toegang te krijgen tot de matrixelementen via System.Array of de interfaces die worden geïmplementeerd, kan echter leiden tot een uitzondering tijdens runtime, omdat aanwijzertypen niet kunnen worden omgezet in object.
  • De impliciete en expliciete verwijzingsconversies (§10.2.8, §10.3.5) van een enkeldimensionaal matrixtype S[] naar System.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 worden geïmplementeerd op alle array_type is van toepassing op aanwijzermatrices.
  • De expliciete verwijzingsconversies (§10.3.5) van System.Collections.Generic.IList<S> en de basisinterfaces naar een enkeldimensionaal matrixtype T[] 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 voor de foreach instructie over matrices die worden beschreven in §9.4.4.17 niet kunnen 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 x voor 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 Vbestaat, wordt er een fout gegenereerd en worden er geen verdere stappen ondernomen. Als x de waarde nullis, wordt er een System.NullReferenceException gegenereerd tijdens runtime.

Opmerking: Hoewel aanwijzertypen niet zijn toegestaan als typeargumenten, kunnen aanwijzermatrices worden gebruikt als typeargumenten. eindnotitie

23.6 Aanwijzers in expressies

23.6.1 Algemeen

In een onveilige context kan een expressie een resultaat opleveren van een aanwijzertype, maar buiten een onveilige context is het een compilatiefout voor een expressie van een type aanwijzer. In precieze termen treedt buiten een onveilige context een compilatietijdfout op als er een simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) of element_access (§12.8.12) van een type aanwijzer is.

In een onveilige context staan de producties primary_no_array_creation_expression (§12.8) en unary_expression (§12.9) extra constructies toe, die worden beschreven in de volgende subclauses.

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. Er is met name geen garantie dat deze bewerking een System.NullReferenceException.

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 23.6.3 Pointer-leden

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 van het formulier P->IP moet een expressie van een type aanwijzer zijn en I wordt een toegankelijk lid van het type waarnaar P punten worden aangegeven.

De toegang van een aanwijzerlid van het formulier P->I wordt precies als (*P).Ivolgt geëvalueerd. Zie §23.6.2 voor een beschrijving van de indirectieoperator van de aanwijzer ().* Zie §12.8.7 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 bewerking P->I precies gelijk is aan (*P).I, kan de Main 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_no_array_creation_expression gevolgd door een expressie tussen "[" en "]".

pointer_element_access
    : primary_no_array_creation_expression '[' expression ']'
    ;

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, longof ulong.

De toegang tot een aanwijzerelement van het formulier P[E] wordt precies zo geëvalueerd als *(P + E). Zie §23.6.2 voor een beschrijving van de indirectieoperator van de aanwijzer ().* Zie §23.6.7 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;
            }
        }
    }
}

een aanwijzerelementtoegang wordt gebruikt om de tekenbuffer in een for lus te initialiseren. Omdat de bewerking P[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 initialiseren p. De toewijzing die *p in feite wordt geïnitialiseerd i, maar de opname van deze initialisatie is de verantwoordelijkheid van de programmeur en er zou geen compilatiefout optreden als de opdracht 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 nemen 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-1respectievelijk (§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 Rekenkundige aanwijzer

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, longof 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* dat het resultaat is van aftrekken N * sizeof(T) van het adres dat is opgegeven door P.

Bij twee expressies en , van een type aanwijzerT*, berekent de expressie P – Q het verschil tussen de adressen die worden opgegeven door P en Q wordt dat verschil vervolgens gedeeld door sizeof(T).QP 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.

Vergelijking van 23.6.8 Aanwijzer

In een onveilige context kunnen de ==operators , , !=, <en >= ><=operators (§12.12) worden toegepast op waarden van alle typen aanwijzers. 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 instructie

In een onveilige context staat de productie van het embedded_statement (§13.1) een extra constructie toe, 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 alle fixed_pointer_initializerdie zich rechts van de declaratie van die variabele voordoen, en in de embedded_statement van de vaste instructie. Een lokale variabele die door een vaste instructie 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 type T, mits het type T* impliciet wordt omgezet in het type aanwijzer dat in de fixed 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 type T* 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 de fixed instructie. Als de matrixexpressie is of als de matrix nul elementen heeft, berekent de initialisatiefunctie een adres dat gelijk is null aan nul.
  • Een expressie van het type string, mits het type char* impliciet wordt geconverteerd naar het type aanwijzer dat in de fixed 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 de fixed instructie. Het gedrag van de fixed instructie is door de implementatie gedefinieerd als de tekenreeksexpressie is null.
  • Een expressie van een ander type dan array_type of, mits er een toegankelijke methode of een toegankelijke uitbreidingsmethode bestaat die overeenkomt met de handtekeningref [readonly] T GetPinnableReference(), waarbij T een unmanaged_type is en T* impliciet kan worden omgezet in het type aanwijzer dat in de fixed instructie is opgegeven.string 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 de fixed instructie. Een GetPinnableReference() methode kan door de fixed instructie worden gebruikt wanneer overbelastingsresolutie (§12.6.4) precies één functielid produceert en dat functielid voldoet aan de voorgaande voorwaarden. De GetPinnableReference methode moet een verwijzing retourneren naar een adres dat gelijk is aan nul, zoals dat wordt geretourneerd wanneer System.Runtime.CompilerServices.Unsafe.NullRef<T>() er geen gegevens zijn om vast te maken.
  • 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 de fixed 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 verplaatsing 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 objectexemplaren niet worden 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 instructie. De eerste instructie corrigeert en verkrijgt het adres van een statisch veld, de tweede instructie corrigeert en verkrijgt het adres van een exemplaarveld, en de derde instructie lost het adres van een matrixelement op en verkrijgt het adres van een matrixelement. 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 is het opgeven van een matrixexemplaren ahetzelfde als het opgeven a[0] van aeen instructie.fixed

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 matrixexemplaren averkrijgt, zijn de aanwijzerwaarden van waaruit p adressen van de elementen in de matrix worden p + a.Length - 1 weergegeven. Op dezelfde manier vertegenwoordigen de variabelen van waaruit p[0] p[a.Length - 1] de werkelijke matrixelementen worden vertegenwoordigd. 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 tekenreeksexemplaren te herstellen, verwijst altijd naar een door null beëindigde tekenreeks. Binnen een vaste instructie die een aanwijzer p naar een tekenreeksexemplaren sverkrijgt, verwijzen de aanwijzerwaarden tussen p p + s.Length ‑ 1 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 toegankelijke GetPinnableReference methode met de juiste handtekening. In de fixed instructie wordt de ref int geretourneerde van die methode wanneer deze wordt aangeroepen c , gebruikt om de int* aanwijzer pte 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 tekenreeksexemplaren null-tekens mag bevatten. Als dergelijke null-tekens aanwezig zijn, wordt de tekenreeks afgekapt weergegeven wanneer deze wordt behandeld als een null-beëindigd 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 vaste grootte worden beschouwd als elementen. Als zodanig wordt het type termelement zoals gedefinieerd voor een matrix ook gebruikt met een buffer met een 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 bufferdeclaratie met een vaste grootte kan een set kenmerken (§22), een new wijzigingsfunctie (§15.3.5), toegankelijkheidsmodifiers bevatten die overeenkomen met een van de aangegeven toegankelijkheidsproblemen die zijn toegestaan 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 buffer(s) aan die door de declaratie zijn ingevoerd. Het type bufferelement is een van de vooraf gedefinieerde typen sbyte, , , ushortintshortbytelonguint, , ulong, , char, , floatof . doublebool

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 id die het lid een naam krijgt, gevolgd door een constante expressie tussen [ 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 inten 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];
}

is gelijk aan

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.

In een lidtoegang tot het formulier E.I waar E. het impliciet this.kan zijn, als E het een struct-type is en een opzoekactie van I een lid in dat structtype een lid met een vaste grootte identificeert, wordt deze 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 Als dit niet een verplaatsbare variabele is (§23.4), dan:
  • E Anders verwijst naar een vaste variabele en het resultaat van de expressie is een aanwijzer naar het eerste element van het bufferlid I met een vaste grootte in E. Het resultaat is van het type S*, waarbij S het elementtype is Ien 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 is, wordt een exemplaarvariabele van een klasse-exemplaar of een matrixelement automatisch geïnitialiseerd op de standaardwaarden van de buffer met vaste grootte (§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.

In een onveilige context als een stackalloc_expression (§12.8.22) voorkomt als de initialisatie-expressie van een local_variable_declaration (§13.6.2), waarbij de local_variable_type ofwel een type aanwijzer (§23.3) of afgeleid (var) is, is het resultaat van de stackalloc_expression een type T * aanwijzer die begint met het toegewezen blok, waar T is de unmanaged_type van de stackalloc_expression.

In alle andere opzichten volgen de semantiek van local_variable_declaration s (§13.6.2) en stackalloc_expressions (§12.8.22) in onveilige contexten die zijn gedefinieerd voorveilige contexten.

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 matrices of stackalloc'ed-blokken van Span<T> het type, is de toegang tot de elementen van een stackalloc'ed-blok met aanwijzer' een onveilige bewerking en wordt het bereik niet 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 de IntToString 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.