Megosztás a következőn keresztül:


23 Nem biztonságos kód

23.1 Általános

Olyan implementációra van szükség, amely nem támogatja a nem biztonságos kódot az ebben a záradékban meghatározott szintaktikai szabályok használatának diagnosztizálásához.

A záradék fennmaradó része, beleértve az összes alklámot, feltételesen normatív.

Megjegyzés: Az előző záradékokban meghatározott alapvető C#-nyelv különösen különbözik a C és a C++ nyelvtől, mivel adattípusként kihagyja a mutatókat. A C# ehelyett hivatkozásokat biztosít, és lehetővé teszi a szemétgyűjtő által felügyelt objektumok létrehozását. Ez a kialakítás más funkciókkal párosítva sokkal biztonságosabb nyelvvé teszi a C#-t, mint a C vagy a C++ nyelvet. Az alapvető C#-nyelvben egyszerűen nem lehet nem inicializált változóval, "dangling" mutatóval vagy olyan kifejezéssel rendelkezni, amely egy tömböt a határán túl indexel. Így a C és C++ programokat rendszeresen sújtó hibák egész kategóriái megszűnnek.

Bár gyakorlatilag minden C vagy C++ mutatótípus-szerkezetnek van egy referenciatípus-megfelelője a C#-ban, mégis vannak olyan helyzetek, amikor szükség van a mutatótípusokhoz való hozzáférésre. Előfordulhat például, hogy a mögöttes operációs rendszerrel való együttműködés, egy memórialeképezett eszköz elérése vagy egy időkritikus algoritmus implementálása nem lehetséges vagy praktikus a mutatók elérése nélkül. Ennek az igénynek a megoldásához a C# lehetővé teszi a nem biztonságos kód írását.

A nem biztonságos kódban deklarálhatók és kezelhetők a mutatók, konverziók végezhetők a mutatók és az integráltípusok között, a változók címének meghatározása stb. Bizonyos értelemben a nem biztonságos kódok írása olyan, mint a C-kód írása egy C#-programban.

A nem biztonságos kód valójában egy "biztonságos" funkció mind a fejlesztők, mind a felhasználók szempontjából. A nem biztonságos kódot egyértelműen meg kell jelölni a módosítóval unsafe, így a fejlesztők nem használhatnak véletlenül nem biztonságos szolgáltatásokat, és a végrehajtási motor úgy működik, hogy a nem biztonságos kód ne hajtható végre nem megbízható környezetben.

végjegyzet

23.2 Nem biztonságos környezetek

A C# nem biztonságos funkciói csak nem biztonságos környezetekben érhetők el. A nem biztonságos környezetet egy módosító típus, tag vagy helyi függvény deklarációjában vagy egy unsafe alkalmazásával vezetik be:

  • Az osztály, a szerkezet, a felület vagy a meghatalmazott deklarációja tartalmazhat unsafe módosító elemet, amely esetben az ilyen típusú deklaráció teljes szöveges terjedelme (beleértve az osztály, a szerkezet vagy az interfész törzsét) nem biztonságos környezetnek minősül.

    Megjegyzés: Ha a type_declaration részleges, csak az adott rész nem biztonságos környezet. végjegyzet

  • Egy mező, metódus, tulajdonság, esemény, indexelő, operátor, példánykonstruktor, finalizer, statikus konstruktor vagy helyi függvény deklarációja tartalmazhat unsafe módosítót, ebben az esetben a tagdeklaráció teljes szöveges terjedelme nem biztonságos környezetnek minősül.
  • A unsafe_statement lehetővé teszik a blokkon belüli nem biztonságos környezet használatát. A társított blokk teljes szöveges terjedelme nem biztonságos környezetnek minősül. A nem biztonságos környezetben deklarált helyi függvény önmagában nem biztonságos.

A kapcsolódó nyelvtani bővítmények alább és az azt követő alklámokban jelennek meg.

unsafe_modifier
    : 'unsafe'
    ;

unsafe_statement
    : 'unsafe' block
    ;

Példa: Az alábbi kódban

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

a unsafe szerkezet deklarációban megadott módosító miatt a szerkezet deklaráció teljes szöveges terjedelme nem biztonságos környezetté válik. Így deklarálható, hogy a Left mezők mutató Right típusúak. A fenti példa is írható

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

Itt a unsafe meződeklarációk módosítói miatt ezek a deklarációk nem biztonságos környezetnek minősülnek.

záró példa

A nem biztonságos környezet kialakításán kívül, amely lehetővé teszi a mutatótípusok használatát, a unsafe módosítónak nincs hatása egy típusra vagy tagra.

Példa: Az alábbi kódban

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

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

a módszer F nem biztonságos módosítója A egyszerűen azt eredményezi, hogy a szöveg terjedelme F nem biztonságos környezetté válik, amelyben a nyelv nem biztonságos funkciói használhatók. A felülbírálás FB során nincs szükség a módosító újbóli megadására unsafe – kivéve persze, ha a F metódusnak önmagában B is hozzá kell férnie a nem biztonságos funkciókhoz.

A helyzet kissé eltérő, ha egy mutatótípus a metódus aláírásának része

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

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

FMivel az aláírás tartalmaz egy mutatótípust, az csak nem biztonságos környezetben írható. A nem biztonságos környezet azonban úgy vezethető be, hogy a teljes osztályt nem biztonságossá teszi, ahogyan az a példában Ais szerepel, vagy a metódusdeklarációban egy módosító is szerepel unsafe , ahogyan az a példában Bis látható.

záró példa

Ha a unsafe módosítót részleges típusdeklarációban használják (15.2.7.§), csak az adott rész minősül nem biztonságos környezetnek.

23.3 Mutatótípusok

Nem biztonságos környezetben a típus (8.1. §) lehet pointer_type, valamint value_type, reference_type vagy type_parameter. Nem biztonságos környezetben a pointer_type tömb elemtípusa is lehet (17. §). A pointer_type nem biztonságos környezeten kívüli kifejezéstípusban (12.8.18. §) is használható (mivel az ilyen használat nem biztonságos).

A pointer_type unmanaged_type (8.8

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

A mutatótípus előtt * megadott típust a mutatótípus hivatkozási típusának nevezzük. Ez annak a változónak a típusát jelöli, amelyre a mutatótípus értéke rámutat.

A pointer_type csak nem biztonságos környezetben használható array_type (23.2. §). A non_array_type minden olyan típus, amely maga nem array_type.

A hivatkozásoktól (referenciatípusok értékeitől) eltérően a mutatókat nem követi nyomon a szemétgyűjtő – a szemétgyűjtő nem ismeri a mutatókat és azokat az adatokat, amelyekre mutatnak. Ezért a mutató nem mutathat hivatkozásra vagy hivatkozásokat tartalmazó szerkezetre, és a mutató hivatkozástípusának unmanaged_type kell lennie. Maguk a mutatótípusok nem felügyelt típusok, ezért egy mutatótípus használható hivatkozástípusként egy másik mutatótípushoz.

A hivatkozások és hivatkozások keverésének intuitív szabálya, hogy a hivatkozások (objektumok) hivatkozásai tartalmazhatnak mutatókat, de a hivatkozások hivatkozásai nem tartalmazhatnak hivatkozásokat.

Példa: Néhány példa a mutatótípusokra az alábbi táblázatban található:

Példa Leírás
byte* Mutató: byte
char* Mutató: char
int** Mutató a mutatóhoz int
int*[] Mutatók egydimenziós tömbje int
void* Mutató ismeretlen típusra

záró példa

Egy adott megvalósításhoz minden mutatótípusnak azonos méretűnek és ábrázoltnak kell lennie.

Megjegyzés: A C és a C++ típustól eltérően, ha több mutatót deklarál ugyanabban a deklarációban, a C#-ban a * program csak az alapul szolgáló típussal együtt írja, nem pedig előtag-írásjelként az egyes mutatóneveken. Példa:

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

végjegyzet

A típussal rendelkező T* mutató értéke egy típusváltozó Tcímét jelöli. A mutató indirekt operátora * (23.6.2. §) használható a változó eléréséhez.

Példa: Adott típusú Pváltozó int* esetén a kifejezés *P a int megadott címen Ptalálható változót jelöli. záró példa

Az objektumhivatkozásokhoz hasonlóan a mutató is lehet null. Ha az indirekt operátort egy null-valued mutatóra alkalmazza, az implementáció által definiált viselkedést eredményez (23.6.2. §). Az értékeket null tartalmazó mutatót az all-bits-zero jelöli.

A void* típus egy ismeretlen típusra mutató mutatót jelöl. Mivel a hivatkozás típusa ismeretlen, az indirekt operátor nem alkalmazható egy ilyen típusú void*mutatóra, és az ilyen mutatón sem végezhető el számtani művelet. A típusmutatók void* azonban bármely más (és fordítva) mutatótípusra vethetők, és összehasonlíthatók más mutatótípusok értékeivel (23.6.8. §).

A mutatótípusok a típusok külön kategóriái. A referenciatípusoktól és az értéktípusoktól eltérően a mutatótípusok nem öröklődnek, object és nem léteznek átalakítások a mutatótípusok és objecta mutatótípusok között. A kurzorok esetében különösen a boxolás és a kicsomagolás (8.3.13. §) nem támogatott. A konverziók azonban engedélyezettek a különböző mutatótípusok és a mutatótípusok és az integráltípusok között. Ezt a 23.5.

A pointer_type nem használható típusargumentumként (8.4.§), és a típuskövetkezőség (12.6.3. §) meghiúsul az olyan általános metódushívások esetében, amelyek egy típusargumentumot mutatótípusra következtetnek.

A pointer_type nem használhatók dinamikusan kötött művelet alexpressziójának típusaként (12.3.3. §).

A pointer_type nem használható a bővítménymetódus első paraméterének típusaként (15.6.10. §).

Illékony mező típusaként pointer_type használható (15.5.4. §).

Egy az a mutatótípus, amelynek hivatkozási típusa a dinamikus törlés.E*

Mutatótípusú kifejezés nem használható egy anonymous_object_creation_expression belüli member_declarator értékének megadására (12.8.17.3. §).

Az alapértelmezett érték (§9.3) minden mutatótípus esetében a következő null.

Megjegyzés: Bár a mutatók átadhatók referenciaparaméterként, de ez meghatározatlan működést okozhat, mivel a mutatót úgy lehet beállítani, hogy egy helyi változóra mutasson, amely már nem létezik a hívott metódus visszatérésekor, vagy az a rögzített objektum, amelyre korábban rámutatott, már nincs javítva. Példa:

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
        }
    }
}

végjegyzet

Egy metódus visszaadhat valamilyen típusú értéket, és ez a típus lehet mutató.

Példa: Ha egy folytonos szekvenciára intmutató mutatót ad, az adott sorozat elemszámát és egy másik int értéket, a következő metódus az adott érték címét adja vissza az adott sorrendben, ha egyezés történik; ellenkező esetben a következőt adja vissza 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;
}

záró példa

Nem biztonságos környezetben számos szerkezet érhető el a mutatókon való működéshez:

  • A unary * operátor használható a mutató indirektálására (23.6.2. §).
  • Az -> operátor használható a szerkezet egy tagjának mutatón keresztüli elérésére (23.6.3. §).
  • Az [] operátor használható mutató indexelésére (23.6.4. §).
  • Egy változó címének lekéréséhez a unary & operátor használható (23.6.5. §).
  • Az ++ és -- operátorok használhatók a mutatók növekményére és dekrementálására (23.6.6. §).
  • A bináris + és - operátorok a mutató aritmetikai végrehajtására használhatók (23.6.7. §).
  • A ==, !=, <, ><=és >= operátorok használhatók a mutatók összehasonlítására (23.6.8. §).
  • Az stackalloc operátor használható a hívásverem memóriájának lefoglalására (23.9.§).
  • Az fixed utasítással ideiglenesen kijavíthatók a változók, így a címe lekérthető (23.7.§).

23.4 Rögzített és áthelyezhető változók

Az operátor címe (23.6.5. §) és az utasítás (fixed. §) a változókat két kategóriába osztja: Rögzített változók és áthelyezhető változók.

A rögzített változók olyan tárolóhelyeken találhatók, amelyeket a szemétgyűjtő működése nem érint. (Rögzített változók például a helyi változók, az értékparaméterek és a mutatók elhalasztásával létrehozott változók.) Másrészt az áthelyezhető változók olyan tárolóhelyeken találhatók, ahol a szemétgyűjtő áthelyezi vagy megsemmisíti azokat. (Az áthelyezhető változók közé tartoznak például az objektumokban és a tömbök elemeiben lévő mezők.)

Az & operátor (23.6.5. §) lehetővé teszi egy rögzített változó címének korlátozás nélküli beszerzését. Mivel azonban egy áthelyezhető változót a szemétgyűjtő áthelyez vagy elidegenít, az áthelyezhető változó címe csak a fixed statement (23.7. §) használatával szerezhető be, és ez a cím csak az adott utasítás időtartamára fixed érvényes.

Pontos értelemben a rögzített változó az alábbiak egyike:

  • Egy helyi változóra, értékparaméterre vagy paramétertömbre hivatkozó simple_name (12.8.4.§) eredő változó, kivéve, ha a változót névtelen függvény rögzíti (12.19.6.2. §).
  • Az űrlap member_access (12.8.7
  • Az űrlap pointer_indirection_expressionváltozó.

Minden más változó áthelyezhető változóként van besorolva.

A statikus mező áthelyezhető változóként van besorolva. A by-reference paraméter is áthelyezhető változóként van besorolva, még akkor is, ha a paraméter argumentuma rögzített változó. Végül egy mutató elhalasztásával előállított változó mindig rögzített változóként van besorolva.

23.5 Mutatókonvertálások

23.5.1 Általános

Nem biztonságos környezetben a rendelkezésre álló implicit konverziók halmaza (10.2. §) ki van terjesztve a következő implicit mutatókonverziókra:

  • A pointer_type típusától a típusig void*.
  • null A literáltól (6.4.5.7. §) minden pointer_type.

Emellett a nem biztonságos környezetben a rendelkezésre álló explicit konverziók készlete (10.3. §) ki van terjesztve a következő explicit mutatókonverziókra:

  • Bármely pointer_type bármely más pointer_type.
  • Az sbyte, byte, short, ushort, int, uint, long, vagy ulong bármely pointer_type.
  • Bármely , , sbyte, byte, short, ushortint, , uint, vagy long.

Végül egy nem biztonságos környezetben a standard implicit konverziók készlete (10.4.2. §) a következő mutatókonverziókat tartalmazza:

  • A pointer_type típusától a típusig void*.
  • null A literáltól a pointer_type.

A két mutatótípus közötti konverziók soha nem módosítják a tényleges mutatóértéket. Más szóval az egyik mutatótípusról a másikra történő átalakításnak nincs hatása az egérmutató által megadott mögöttes címre.

Ha az egyik mutatótípust egy másikra konvertálja, ha az eredményül kapott mutató nincs megfelelően igazítva a hegyes típushoz, a viselkedés nem lesz meghatározva, ha az eredmény hareferens. A "helyesen igazított" fogalom általában tranzitív: ha a beíráshoz A helyesen igazított mutató a beíráshoz B, amely viszont helyesen van igazítva a mutatóhoz a beíráshoz C, akkor a begépelendő A mutató megfelelően van igazítva a beíráshoz C.

Példa: Fontolja meg az alábbi esetet, amikor egy egy típussal rendelkező változó egy másik típushoz mutatóval érhető el:

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
}

záró példa

Ha egy mutatótípust mutatóvá bytealakít át, az eredmény a változó legalacsonyabb címére byte mutat. Az eredmény egymást követő növekményei a változó méretétől függően a változó fennmaradó bájtjaira mutatnak.

Példa: Az alábbi módszer a nyolc bájt double mindegyikét hexadecimális értékként jeleníti meg:

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

Az előállított kimenet természetesen az endianitástól függ. Az egyik lehetőség a " BA FF 51 A2 90 6C 24 45".

záró példa

Az egérmutatók és az egész számok közötti leképezések implementálási definícióval vannak meghatározva.

Megjegyzés: A lineáris címtérrel rendelkező 32 és 64 bites PROCESSZORarchitektúrák esetében azonban a mutatók integráltípusokra vagy azok alapján történő konvertálása általában pontosan úgy viselkedik, mint az adott integráltípusok átalakítása uint vagy ulong értékei. végjegyzet

23.5.2 Mutatótömbök

A mutatótömbök nem biztonságos környezetben array_creation_expression (12.8.17.4. §) használatával hozhatók létre. Csak a más tömbtípusokra vonatkozó átalakítások némelyike engedélyezett a mutatótömbökben:

  • Az implicit referenciaátalakítás (§10.2.8) bármely array_typeSystem.Array és az általa implementálandó felületekre is vonatkozik a mutatótömbökre. A tömbelemeknek az általa implementálandó felületeken vagy felületeken keresztüli System.Array elérésére tett kísérletek azonban futásidőben kivételt okozhatnak, mivel a mutatótípusok nem konvertálhatók object.
  • Az implicit és explicit referenciakonvertálások (§10.2.8, §10.3.5) egydimenziós tömbtípusból S[]System.Collections.Generic.IList<T> a mutatótömbökre és azok általános alapfelületére soha nem vonatkoznak.
  • A mutatótömbökre az explicit referenciaátalakítás (System.Array. §) és az általa implementálandó felületek array_type vonatkoznak.
  • A mutatótömbökre soha nem vonatkoznak az explicit referenciakonvertálások (10.3.5.§) System.Collections.Generic.IList<S> és alapillesztői egydimenziós tömbtípusra T[] , mivel a mutatótípusok nem használhatók típusargumentumként, és a mutatótípusoktól a nem mutató típusúakig nincs átalakítás.

Ezek a korlátozások azt jelentik, hogy a foreach9.4.4.17 . Ehelyett az foreach űrlap egy utasítása

foreach (V v in x) embedded_statement

ahol a típus x az űrlap T[,,...,]tömbtípusa, n a dimenziók száma mínusz 1, vagy TV mutatótípus, az alábbi beágyazott for-loopok használatával van kibontva:

{
    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*
            }
        }
    }
}

A változók a, i0, i1... in nem láthatók vagy nem érhetők el x a program embedded_statement vagy bármely más forráskódja számára. A változó v írásvédett a beágyazott utasításban. Ha nem történik explicit átalakítás (23.5. §) az elemtípusból T a következőre V, a rendszer hibát okoz, és nem történik további lépés. Ha x rendelkezik az értékkel null, a rendszer futásidőben ad ki egy System.NullReferenceException értéket.

Megjegyzés: Bár a mutatótípusok nem használhatók típusargumentumként, a mutatótömbök típusargumentumként használhatók. végjegyzet

23.6 A kifejezések mutatói

23.6.1 Általános

Nem biztonságos környezetben a kifejezések mutatótípust eredményezhetnek, de a nem biztonságos környezeten kívül fordítási idő hibát jelent, ha egy kifejezés mutatótípusú. Pontosan kifejezve, a nem biztonságos környezeten kívül fordítási időhiba akkor fordul elő, ha bármely simple_name (12.8.4.§), member_access (12.8.7.§), invocation_expression (12.8.10. §) vagy element_access (12.8.12. §) mutató típusú.

Nem biztonságos környezetben a primary_expression (12.8. §) és a unary_expression (12.9. §) produkciók további szerkezeteket tesznek lehetővé, amelyeket az alábbi alcikkekben ismertetünk.

Megjegyzés: A nem biztonságos operátorok elsőbbséget és asszociativitását a nyelvhelyesség határozza meg. végjegyzet

23.6.2 Mutató indirekt

A pointer_indirection_expression egy csillagból (*) és egy unary_expression áll.

pointer_indirection_expression
    : '*' unary_expression
    ;

A unary * operátor indirekt mutatót jelöl, és annak a változónak a lekérésére szolgál, amelyre a mutató mutat. A kiértékelés *Peredménye – ahol P egy mutató típusú T*kifejezés – típusváltozó T. Fordítási idő hiba a nem szereplő * operátor alkalmazása egy típuskifejezésre void* vagy egy olyan kifejezésre, amely nem mutató típusú.

A unary * operátor egy -valued mutatóra nullvaló alkalmazásának hatása implementálási definíció. Különösen nincs garancia arra, hogy ez a művelet egy System.NullReferenceException.

Ha érvénytelen érték van hozzárendelve az egérmutatóhoz, a nem szereplő * operátor viselkedése nincs meghatározva.

Megjegyzés: A mutató unary * operátor általi elhalasztásának érvénytelen értékei között szerepel egy olyan cím, amely nem megfelelően van igazítva a mutatott típushoz (lásd a 23.5. §-ban szereplő példát), valamint egy változó címét az élettartam lejárta után.

Határozott hozzárendelés-elemzés céljából az űrlap *P kifejezésének kiértékelésével előállított változót kezdetben hozzárendeltnek kell tekinteni (9.4.2. §).

23.6.3 Mutatótag-hozzáférés

A pointer_member_access egy primary_expression, majd egy "->" jogkivonatból, majd egy azonosítóból és egy választható type_argument_list áll.

pointer_member_access
    : primary_expression '->' identifier type_argument_list?
    ;

Az űrlap P->IP mutatótag-hozzáférése egy mutatótípus kifejezése, és I annak a típusnak egy akadálymentes tagját jelöli, amelyhez P a pontokat hozzá kell adni.

Az űrlap P->I mutatótag-hozzáférése pontosan a következőképpen (*P).Ilesz kiértékelve. A mutató indirekt operátorának leírását lásd* a 23.6.2. A taghozzáférés-operátor (.) leírását lásd a 12.8.7.

Példa: Az alábbi kódban

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

az operátor a -> mezők elérésére és egy mutatón keresztüli szerkezet metódusának meghívására szolgál. Mivel a művelet P->I pontosan egyenértékű a művelettel (*P).I, a Main módszer ugyanilyen jól írható volna:

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

záró példa

23.6.4 Mutatóelem-hozzáférés

A pointer_element_access egy primary_expression-ből áll, amelyet egy kifejezés követ, amely „[” és „]” közé van zárva.

pointer_element_access
    : primary_expression '[' expression ']'
    ;

A primary_expression felismerésekor, ha a element_access és a pointer_element_access (23.6.4. §) is alkalmazható, akkor az utóbbit kell választani, ha a beágyazott primary_expression mutatótípusú (23.3. §).

Az űrlap P[E]mutatóelem-hozzáférésében nem mutató típusú Pkifejezésnek kell lennie, void* és E olyan kifejezésnek kell lennie, amely implicit módon átalakítható int, uintvagy longulong.

Az űrlap P[E] mutatóelem-hozzáférése pontosan a következőképpen *(P + E)lesz kiértékelve. A mutató indirekt operátorának leírását lásd* a 23.6.2. A mutató összeadási operátorának (+) leírását lásd a 23.6.7.

Példa: Az alábbi kódban

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

a mutatóelem-hozzáférés a karakterpuffer inicializálására szolgál egy for hurokban. Mivel a művelet pontosan egyenértékű a példával P[E]*(P + E), a példa ugyanilyen jól írható volna:

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

záró példa

A mutatóelem-hozzáférés-operátor nem ellenőrzi a határtalan hibákat, és a határon kívüli elemek elérésekor a viselkedés nincs meghatározva.

Megjegyzés: Ez ugyanaz, mint a C és a C++. végjegyzet

23.6.5 Az operátor címe

A addressof_expression egy amperből (&) és egy unary_expression áll.

addressof_expression
    : '&' unary_expression
    ;

A rögzített változóként (23.4E Az eredmény típusa értékként van T* besorolva. Fordítási időhiba akkor fordul elő, ha E nincs változóként besorolva, ha E írásvédett helyi változóként van besorolva, vagy áthelyezhető E változót jelöl. Az utolsó esetben egy rögzített utasítás (23.7. §) segítségével ideiglenesen "kijavíthatja" a változót a cím lekérése előtt.

Megjegyzés: A 12.8.7. §-ban leírtak szerint a mezőt meghatározó readonly szerkezet vagy osztály példánykonstruktorán vagy statikus konstruktorán kívül ez a mező értéknek minősül, nem pedig változónak. Ezért a címe nem vehető fel. Hasonlóképpen, az állandó címe nem vehető fel. végjegyzet

Az & operátor nem követeli meg, hogy az argumentuma biztosan hozzá legyen rendelve, de egy & művelet után a változó, amelyre az operátort alkalmazza, határozottan hozzárendeltnek kell lennie abban a végrehajtási útvonalban, amelyben a művelet végbement. A programozó feladata annak biztosítása, hogy a változó helyes inicializálása valóban ebben a helyzetben történjen.

Példa: Az alábbi kódban

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

i az inicializáláshoz használt művelet után &i egyértelműen hozzárendeltnek pminősül. A tényleges hozzárendelés *p inicializálódik i, de ennek az inicializálásnak a belefoglalása a programozó feladata, és a hozzárendelés eltávolításakor nem fordul elő fordítási idő hiba.

záró példa

Megjegyzés: Az operátor határozott & hozzárendelési szabályai léteznek, így elkerülhető a helyi változók redundáns inicializálása. Sok külső API például egy olyan struktúrára mutat, amelyet az API tölt ki. Az ilyen API-k hívásai általában egy helyi szerkezetváltozó címét adják át, és a szabály nélkül a szerkezetváltozó redundáns inicializálására lenne szükség. végjegyzet

Megjegyzés: Ha egy helyi változót, értékparamétert vagy paramétertömböt egy névtelen függvény rögzít (§12.8.24), akkor a helyi változó, paraméter vagy paramétertömb már nem minősül rögzített változónak (23.7.§), hanem áthelyezhető változónak minősül. Ezért hiba, ha egy nem biztonságos kód egy névtelen függvény által rögzített helyi változó, értékparaméter vagy paramétertömb címét veszi fel. végjegyzet

23.6.6 Mutató növekménye és decrement

Nem biztonságos környezetben az és operátorok ++ (-- és §12.9.6) minden típusú mutatóváltozóra alkalmazhatók, kivéve.void* Így minden mutatótípus T*esetében a következő operátorok implicit módon vannak definiálva:

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

Az operátorok ugyanazokat az eredményeket eredményezik, mint x+1 a x-123.6.7. Más szóval, egy típusú mutatóváltozó T*esetében az ++ operátor hozzáadja sizeof(T) a változóban található címet, az -- operátor pedig kivonja sizeof(T) a változó címéből.

Ha egy mutató növekményes vagy csökkenő művelete túlcsordul a mutatótípus tartományán, az eredmény implementációban van definiálva, de nem jön létre kivétel.

23.6.7 Mutató aritmetikai

Nem biztonságos környezetben az operátor (+. §) és az operátor (-. §) az összes mutatótípus értékeire alkalmazható, kivéve.void* Így minden mutatótípus T*esetében a következő operátorok implicit módon vannak definiálva:

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);

Adott egy P mutatótípus T* kifejezése és egy , N, intvagy uint, típusú longkifejezésulong, a kifejezésekP + N, és N + P kiszámítja a mutató értékétT*, amely a megadott N * sizeof(T)címhez való hozzáadásból P ered. Hasonlóképpen a kifejezés P – N kiszámítja a mutató típusú T* értéket, amely a megadott címből N * sizeof(T)való kivonásból P származik.

Két kifejezés és PQmutató típusú T*kifejezés esetén a kifejezés P – Q kiszámítja a megadott címek PQ közötti különbséget, majd osztja el a különbséget a következővel sizeof(T): . Az eredmény típusa mindig long. Valójában P - Q a számítás a ((long)(P) - (long)(Q)) / sizeof(T)következőképpen történik: .

Példa:

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}");
        }
    }
}

amely a kimenetet állítja elő:

p - q = -14
q - p = 14

záró példa

Ha a mutató aritmetikai művelete túlcsordul a mutatótípus tartományán, az eredményt implementáció által meghatározott módon csonkolja, de a rendszer nem hoz létre kivételeket.

23.6.8 Mutató összehasonlítása

Nem biztonságos környezetben a ==, !=, <, ><=, és >= operátorok (12.12.§) alkalmazhatók az összes mutatótípus értékeire. A mutató-összehasonlító operátorok a következők:

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);

Mivel implicit átalakítás létezik bármilyen mutatótípusból a void* típusba, bármely mutatótípus operandusai összehasonlíthatók ezekkel az operátorokkal. Az összehasonlító operátorok úgy hasonlítják össze a két operandus által megadott címeket, mintha aláíratlan egész számok lennének.

23.6.9 Az operátor mérete

Bizonyos előre definiált típusok esetében (12.8.19. §) az sizeof operátor állandó int értéket ad. Minden más típus esetében az sizeof operátor eredménye implementálási definícióval rendelkezik, és értékként, nem állandóként van besorolva.

Az a sorrend, amelyben a tagok egy szerkezetbe vannak csomagolva, meghatározatlan.

Igazítás céljából előfordulhat, hogy a szerkezet elején, egy szerkezeten belül és a szerkezet végén névtelen kitöltés van. A párnázásként használt bitek tartalma meghatározatlan.

A szerkezettípussal rendelkező operandusokra alkalmazva az eredmény az adott típusú változóban lévő bájtok teljes száma, beleértve a kitöltéseket is.

23.7 A rögzített utasítás

Nem biztonságos környezetben a embedded_statement (§13.1) termelése lehetővé teszi egy további szerkezetet, a rögzített utasítást, amelyet egy áthelyezhető változó "kijavítására" használnak, így a címe állandó marad az utasítás időtartamára.

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
    ;

Minden fixed_pointer_declarator deklarálja az adott pointer_type egy helyi változóját, és inicializálja azt a helyi változót a megfelelő fixed_pointer_initializer által kiszámított címmel. A rögzített utasításban deklarált helyi változó minden olyan fixed_pointer_initializerelérhető, amely a változó deklarációjának jobb oldalán történik, valamint a rögzített utasítás embedded_statement . A rögzített utasítással deklarált helyi változó írásvédettnek minősül. Fordítási időhiba akkor fordul elő, ha a beágyazott utasítás megkísérli módosítani ezt a helyi változót (hozzárendeléssel vagy operátorokkal ++-- ), vagy hivatkozási vagy kimeneti paraméterként adja át.

Hiba egy rögzített helyi változó (§12.19.6.2), értékparaméter vagy paramétertömb használata egy fixed_pointer_initializer. A fixed_pointer_initializer a következők egyike lehet:

  • A "&" jogkivonat, amelyet egy variable_reference (9.5. §) követ egy nem felügyelt típusú mozgatható változóra (T. §), feltéve hogy a típus T* implicit módon átalakítható az utasításban fixed megadott mutatótípusra. Ebben az esetben az inicializáló kiszámítja az adott változó címét, és a változó garantáltan rögzített címen marad a rögzített utasítás időtartama alatt.
  • Nem felügyelt típusú elemeket tartalmazó array_typeT, feltéve, hogy a típus T* implicit módon konvertálható a rögzített utasításban megadott mutatótípusra. Ebben az esetben az inicializáló kiszámítja a tömb első elemének címét, és a teljes tömb garantáltan rögzített címen marad az fixed utasítás időtartama alatt. Ha a tömbkifejezés nulla null , vagy ha a tömb nulla elemekkel rendelkezik, az inicializáló nullával egyenlő címet számít ki.
  • Egy típuskifejezés string, feltéve, hogy a típus char* implicit módon konvertálható az utasításban fixed megadott mutatótípusra. Ebben az esetben az inicializáló kiszámítja a sztring első karakterének címét, és a teljes sztring garantáltan rögzített címen marad az fixed utasítás időtartama alatt. Az utasítás viselkedése fixed implementációban van definiálva, ha a sztringkifejezés .null
  • Nem array_type vagy string– feltéve, hogy létezik az aláírásnak ref [readonly] T GetPinnableReference()megfelelő akadálymentes vagy akadálymentes bővítménymetódus –, ahol Tunmanaged_type, és T* implicit módon átalakítható az fixed utasításban megadott mutatótípusra. Ebben az esetben az inicializáló kiszámítja a visszaadott változó címét, és ez a változó garantáltan rögzített címen marad az fixed utasítás időtartama alatt. A GetPinnableReference() metódus akkor használható az utasítással, ha a fixed túlterhelés feloldása (12.6.4. §) pontosan egy függvénytagot állít elő, és ez a függvénytag megfelel az előző feltételeknek. A GetPinnableReference metódusnak nullával egyenlő címre mutató hivatkozást kell visszaadnia, például akkor System.Runtime.CompilerServices.Unsafe.NullRef<T>() , ha nincs rögzítendő adat.
  • Egy áthelyezhető változó rögzített méretű puffertagjára hivatkozó simple_name vagy member_access , feltéve, hogy a rögzített méretű puffertag típusa implicit módon átalakítható az utasításban fixed megadott mutatótípusra. Ebben az esetben az inicializáló kiszámítja a rögzített méretű puffer első elemére mutató mutatót (23.8.3. §), és a rögzített méretű puffer garantáltan rögzített címen marad az fixed utasítás időtartama alatt.

A fixed_pointer_initializer által alatt.

Példa: Ha egy fixed_pointer_initializer által kiszámított cím egy objektum vagy egy tömbpéldány egy elemének mezőjére hivatkozik, a rögzített utasítás garantálja, hogy a benne lévő objektumpéldány nem lesz áthelyezve vagy nem helyezhető el az utasítás élettartama alatt. záró példa

A programozó felelőssége annak biztosítása, hogy a rögzített utasítások által létrehozott mutatók ne maradjanak túl ezen utasítások végrehajtásán túl.

Példa: Amikor az utasítások által fixed létrehozott mutatókat külső API-knak adnak át, a programozó felelőssége annak biztosítása, hogy az API-k ne tároljanak memóriát ezekről a mutatókról. záró példa

A rögzített objektumok a halom töredezettségét okozhatják (mert nem helyezhetők át). Ezért az objektumokat csak akkor kell rögzíteni, ha feltétlenül szükséges, majd csak a lehető legrövidebb ideig.

Példa: A példa

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);
        }
    }
}

az utasítás több használatát is szemlélteti fixed . Az első utasítás kijavítja és lekéri egy statikus mező címét, a második utasítás kijavítja és lekéri egy példánymező címét, a harmadik utasítás pedig kijavítja és lekéri egy tömbelem címét. Minden esetben hiba lett volna a normál & operátor használata, mivel a változók mind áthelyezhető változókként vannak besorolva.

A fenti példában szereplő harmadik és negyedik fixed állítás azonos eredményt ad. A tömbpéldányok aesetében általában az utasításban a[0] megadott fixed érték ugyanaz, mint egyszerűen megadvaa.

záró példa

Nem biztonságos környezetben az egydimenziós tömbök tömbelemeit növekvő indexrendben tárolja a rendszer, kezdve az indexel 0 , és indexgel Length – 1végződik. Többdimenziós tömbök esetén a tömbelemek tárolása úgy történik, hogy a jobb szélső dimenzió indexei előbb növekedjenek, majd a következő bal dimenzió, és így tovább balra.

fixed Egy tömbpéldányra pmutató mutatót a beolvasó utasításban a mutató értékei p a tömb elemeinek címeit jelölikp + a.Length - 1. Hasonlóképpen, a változók a p[0] tényleges tömbelemeket jelölik p[a.Length - 1] . A tömbök tárolási módjának megfelelően bármely dimenzióból álló tömb úgy kezelhető, mintha lineáris lenne.

Példa:

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

amely a kimenetet állítja elő:

[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

záró példa

Példa: Az alábbi kódban

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);
        }
    }
}

egy fixed utasítással kijavíthatók a tömbök, így a cím átadható egy mutatót használó metódusnak.

záró példa

A char* sztringpéldányok javításával előállított érték mindig null értékű sztringre mutat. Egy rögzített utasításon belül, amely egy sztringpéldányhoz pszerez be mutatóts, a mutató értéke a sztringben szereplő karakterek címeinek megjelenítéséig pp + s.Length ‑ 1 terjed, a mutató értéke p + s.Length pedig mindig null karakterre (a "\0" értékkel rendelkező karakterre) mutat.

Példa:

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);
        }
    }
}

záró példa

Példa: Az alábbi kód egy fixed_pointer_initializer jelenít meg, amely nem array_type vagy 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)
        {
            // ...
        }
    }
}

A típus C megfelelő aláírással rendelkező, akadálymentes GetPinnableReference metódussal rendelkezik. Az utasításban az fixedref int adott metódusból visszaadott, amikor be c van hívva, a mutató int*inicializálására p szolgál. záró példa

A felügyelt típusú objektumok rögzített mutatókkal történő módosítása nem definiált viselkedést eredményezhet.

Megjegyzés: Például mivel a sztringek nem módosíthatók, a programozó felelőssége annak biztosítása, hogy a rögzített sztringre mutató mutató által hivatkozott karakterek ne módosuljanak. végjegyzet

Megjegyzés: A sztringek automatikus null-végződése különösen kényelmes, ha olyan külső API-kat hív meg, amelyek "C stílusú" sztringeket várnak. Vegye figyelembe azonban, hogy a sztringpéldányok null karaktereket tartalmazhatnak. Ha ilyen null karakterek vannak jelen, a sztring csonkoltként jelenik meg, amikor null-végződésűként kezelik char*. végjegyzet

23.8 Rögzített méretű pufferek

23.8.1 Általános

A rögzített méretű pufferek a "C stílusú" sorközi tömbök deklarálására szolgálnak a szerkezetek tagjaiként, és elsősorban a nem felügyelt API-kkal való együttműködéshez hasznosak.

23.8.2 Rögzített méretű pufferdeklarációk

A rögzített méretű puffer olyan tag, amely egy adott típusú változók rögzített hosszúságú pufferének tárolását jelöli. A rögzített méretű pufferdeklarációk egy vagy több rögzített méretű puffert vezetnek be egy adott elemtípushoz.

Megjegyzés: A tömbhöz hasonlóan a rögzített méretű pufferek is elemeket tartalmazónak tekinthetők. Így a tömbhöz definiált elemtípust rögzített méretű pufferrel is használja a rendszer. végjegyzet

A rögzített méretű pufferek csak a szerkezet deklarációiban engedélyezettek, és csak nem biztonságos környezetekben fordulhatnak elő (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 ']'
    ;

A rögzített méretű pufferdeklaráció tartalmazhat attribútumokat (22. §), new módosítókat (15.3.5. §), akadálymentességi módosítókat, amelyek megfelelnek a tagozattagok számára engedélyezett bármely deklarált hozzáférési képességnek (16.4.3. §) és módosítónak unsafe (23.2. §). Az attribútumok és módosítók a rögzített méretű pufferdeklaráció által deklarált összes tagra vonatkoznak. Hiba, hogy ugyanaz a módosító többször is megjelenik egy rögzített méretű pufferdeklarációban.

Rögzített méretű pufferdeklaráció nem tartalmazhatja a static módosítót.

A rögzített méretű pufferdeklaráció pufferelem-típusa határozza meg a deklaráció által bevezetett pufferek elemtípusát. A pufferelem típusa az előre definiált típusok sbyteegyike, byte, , short, ushort, intuintlongulongchar, float, doublevagy .bool

A pufferelem típusát a rögzített méretű pufferdeklarátorok listája követi, amelyek mindegyike új tagot vezet be. A rögzített méretű pufferdeklarátor egy olyan azonosítóból áll, amely a tag nevét, majd egy állandó kifejezést [ tartalmaz és ] jogkivonatokat tartalmaz. Az állandó kifejezés a rögzített méretű pufferdeklarátor által bevezetett tag elemeinek számát jelöli. Az állandó kifejezés típusa implicit módon átalakítható típussá int, az érték pedig nem nulla pozitív egész szám lehet.

A rögzített méretű puffer elemeit egymás után kell elhelyezni a memóriában.

A rögzített méretű pufferdeklaráció, amely több rögzített méretű puffert deklarál, egyenértékű egy azonos attribútumokkal és elemtípusokkal rendelkező rögzített méretű pufferdeklaráció több deklarációjával.

Példa:

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

egyenértékű a

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

záró példa

23.8.3 Rögzített méretű pufferek kifejezésekben

A rögzített méretű puffertag tagkeresése (12.5. §) pontosan úgy halad, mint egy mező tagkeresése.

Rögzített méretű pufferre simple_name (12.8.4.§), member_access (12.8.7. §) vagy element_access (12.8.12. §) hivatkozhat.

Ha egy rögzített méretű puffertagra egyszerű névként hivatkozik, az effektus ugyanaz, mint az űrlap this.Itaghozzáférése, ahol I a rögzített méretű puffertag.

Az űrlap E.I egy taghozzáférésében, ahol E. az implicit this., ha E egy strukturált típus, és az adott struktúratípus tagkeresése I azonosítja a rögzített méretű tagot, akkor E.I a rendszer a következőképpen értékeli ki és sorolja be:

  • Ha a kifejezés E.I nem biztonságos környezetben fordul elő, fordítási időhiba lép fel.
  • Ha E értékként van besorolva, fordítási időhiba lép fel.
  • Ellenkező esetben, ha E egy mozgatható változó (23.4. §) akkor:
    • Ha a kifejezés E.I egy fixed_pointer_initializer (23.7.§), akkor a kifejezés eredménye a rögzített méretű puffertag IEelső elemére mutató mutató.
    • Ellenkező esetben, ha a kifejezés E.Iprimary_expression (12.8.12.1. §) az űrlap egy element_access (E.I[J]), akkor az eredmény E.I egy mutató, Pamely a rögzített méretű puffertag IEelső elemére mutat, és a belefoglaló element_accesspointer_element_access (23.6.4. §) P[J]lesz kiértékelve.
    • Ellenkező esetben fordítási időhiba lép fel.
  • Ellenkező esetben egy rögzített változóra hivatkozik, E és a kifejezés eredménye a rögzített méretű puffertag IEelső elemére mutató mutató. Az eredmény típus S*, ahol az S az elem típusa I, és értékként van besorolva.

A rögzített méretű puffer további elemei az első elem mutatóműveleteinek használatával érhetők el. A tömbökhöz való hozzáféréssel ellentétben a rögzített méretű puffer elemeihez való hozzáférés nem biztonságos művelet, és nem ellenőrzi a tartományt.

Példa: Az alábbiak egy rögzített méretű puffertaggal rendelkező szerkezetet deklarálnak és használnak.

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);
    }
}

záró példa

23.8.4 Határozott hozzárendelés-ellenőrzés

A rögzített méretű pufferek nem tartoznak meghatározott hozzárendelés-ellenőrzés alá (9.4. §), és a rögzített méretű puffertagokat a rendszer figyelmen kívül hagyja a szerkezettípus változóinak határozott hozzárendelés-ellenőrzése céljából.

Ha a rögzített méretű puffertag strukturált változóját tartalmazó legkülső változó statikus változó, egy osztálypéldány példányváltozója vagy tömbelem, a rögzített méretű puffer elemei automatikusan inicializálódnak az alapértelmezett értékekre (9.3. §). Minden más esetben a rögzített méretű puffer kezdeti tartalma nincs meghatározva.

23.9 Veremfoglalás

Az operátorral kapcsolatos általános információkért lásd stackalloc. Itt azt tárgyaljuk, hogy az operátor képes-e mutatót eredményezni.

Ha egy stackalloc_expression egy local_variable_declaration inicializálási kifejezéseként történik (§13.6.2), ahol a local_variable_type vagy egy mutatótípus (§23.3), vagy következtetett (var), a stackalloc_expression eredménye egy T*típusú mutató lesz, ahol T a unmanaged_type a stackalloc_expression. Ebben az esetben az eredmény egy mutató, amely a lefoglalt blokk elejére mutat.

Példa:

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 };
}

záró példa

A tömbökhöz vagy stackalloca "ed block of Span<T> type"-hez való hozzáféréssel ellentétben a mutató típusú ed blokk elemeihez stackallocvaló hozzáférés nem biztonságos művelet, és nem ellenőrzi a tartományt.

Példa: Az alábbi kódban

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));
    }
}

a stackalloc metódus egy IntToString 16 karakterből álló puffer lefoglalására használ egy kifejezést a veremen. A rendszer automatikusan elveti a puffert, amikor a metódus visszatér.

Vegye figyelembe azonban, hogy az IntToString átírható csökkentett módban, azaz mutatók használata nélkül, az alábbiak szerint:

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

záró példa

A feltételesen normatív szöveg vége.