Condividi tramite


23 Codice non sicuro

23.1 Generale

Un'implementazione che non supporta il codice unsafe è necessaria per diagnosticare qualsiasi utilizzo delle regole sintattiche definite in questa clausola.

La parte restante di questa clausola, incluse tutte le relative sottoclause, è normativa in modo condizionale.

Nota: il linguaggio C# principale, come definito nelle clausole precedenti, differisce in particolare da C e C++ nell'omissione dei puntatori come tipo di dati. C# offre invece riferimenti e la possibilità di creare oggetti gestiti da un Garbage Collector. Questa progettazione, abbinata ad altre funzionalità, rende C# un linguaggio molto più sicuro rispetto a C o C++. Nel linguaggio C# principale non è possibile avere semplicemente una variabile non inizializzata, un puntatore "dangling" o un'espressione che indicizza una matrice oltre i limiti. Tutte le categorie di bug che affliggono regolarmente i programmi C e C++ vengono quindi eliminati.

Anche se praticamente ogni costrutto di tipo puntatore in C o C++ ha una controparte di tipo riferimento in C#, tuttavia, ci sono situazioni in cui l'accesso ai tipi di puntatore diventa una necessità. Ad esempio, l'interazione con il sistema operativo sottostante, l'accesso a un dispositivo mappato alla memoria o l'implementazione di un algoritmo critico per il tempo potrebbe non essere possibile o pratico senza l'accesso ai puntatori. Per soddisfare questa esigenza, C# offre la possibilità di scrivere codice non sicuro.

Nel codice non sicuro è possibile dichiarare e operare sui puntatori, per eseguire conversioni tra puntatori e tipi integrali, per accettare l'indirizzo delle variabili e così via. In un certo senso, la scrittura di codice non sicuro è molto simile alla scrittura di codice C all'interno di un programma C#.

Il codice non sicuro è infatti una funzionalità "sicura" dal punto di vista sia degli sviluppatori che degli utenti. Il codice non sicuro deve essere chiaramente contrassegnato con il modificatore unsafe, in modo che gli sviluppatori non possano usare accidentalmente funzionalità non sicure e il motore di esecuzione funzioni per garantire che il codice non sicuro non possa essere eseguito in un ambiente non attendibile.

nota finale

23.2 Contesti non sicuri

Le funzionalità non sicure di C# sono disponibili solo in contesti non sicuri. Un contesto non sicuro viene introdotto includendo un unsafe modificatore nella dichiarazione di un tipo, di un membro o di una funzione locale oppure usando un unsafe_statement:

  • Una dichiarazione di una classe, uno struct, un'interfaccia o un delegato può includere un unsafe modificatore, nel qual caso l'intero extent testuale di tale dichiarazione di tipo (incluso il corpo della classe, dello struct o dell'interfaccia) è considerato un contesto non sicuro.

    Nota: se il type_declaration è parziale, solo quella parte è un contesto non sicuro. nota finale

  • Una dichiarazione di un campo, un metodo, una proprietà, un evento, un indicizzatore, un operatore, un costruttore di istanza, un finalizzatore, un costruttore statico o una funzione locale può includere un unsafe modificatore, nel qual caso, l'intero extent testuale della dichiarazione membro viene considerato un contesto non sicuro.
  • Un unsafe_statement abilita l'uso di un contesto non sicuro all'interno di un blocco. L'intero extent testuale del blocco associato è considerato un contesto non sicuro. Una funzione locale dichiarata all'interno di un contesto unsafe non è sicura.

Le estensioni grammaticali associate sono illustrate di seguito e nelle sottoclause successive.

unsafe_modifier
    : 'unsafe'
    ;

unsafe_statement
    : 'unsafe' block
    ;

Esempio: nel codice seguente

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

Il unsafe modificatore specificato nella dichiarazione di struct fa sì che l'intero extent testuale della dichiarazione di struct diventi un contesto non sicuro. Pertanto, è possibile dichiarare i Left campi e Right come di un tipo di puntatore. L'esempio precedente può anche essere scritto

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

In questo caso, i unsafe modificatori nelle dichiarazioni di campo fanno sì che tali dichiarazioni vengano considerate contesti non sicuri.

esempio finale

Oltre a stabilire un contesto non sicuro, consentendo così l'uso di tipi di puntatore, il unsafe modificatore non ha alcun effetto su un tipo o un membro.

Esempio: nel codice seguente

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

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

il modificatore unsafe nel F metodo in A semplicemente fa sì che l'extent testuale di F diventi un contesto non sicuro in cui è possibile usare le funzionalità non sicure del linguaggio. Nell'override di F in Bnon è necessario specificare nuovamente il unsafe modificatore, a meno che, naturalmente, il F metodo in B sé non debba accedere alle funzionalità non sicure.

La situazione è leggermente diversa quando un tipo di puntatore fa parte della firma del metodo

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

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

In questo caso, poiché Fla firma include un tipo di puntatore, può essere scritta solo in un contesto non sicuro. Tuttavia, il contesto unsafe può essere introdotto rendendo l'intera classe non sicura, come nel caso in Ao includendo un unsafe modificatore nella dichiarazione del metodo, come nel caso in B.

esempio finale

Quando il unsafe modificatore viene utilizzato in una dichiarazione di tipo parziale (§15.2.7), solo tale parte è considerata un contesto non sicuro.

23.3 Tipi di puntatore

In un contesto non sicuro, un tipo (§8.1) può essere un pointer_type e un value_type, un reference_type o un type_parameter. In un contesto non sicuro un pointer_type può anche essere il tipo di elemento di una matrice (§17). Un pointer_type può essere usato anche in un'espressione typeof (§12.8.18) al di fuori di un contesto non sicuro (come tale utilizzo non è sicuro).

Un pointer_type viene scritto come unmanaged_type (§8.8) o la parola chiave void, seguita da un * token:

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

Il tipo specificato prima di * in un tipo di puntatore viene chiamato tipo referenziale del tipo di puntatore. Rappresenta il tipo della variabile a cui punta un valore del tipo di puntatore.

Un pointer_type può essere utilizzato solo in un array_type in un contesto non sicuro (§23.2). Un non_array_type è qualsiasi tipo che non è un array_type.

A differenza dei riferimenti (valori dei tipi di riferimento), i puntatori non vengono rilevati dal Garbage Collector. Il Garbage Collector non ha alcuna conoscenza dei puntatori e dei dati a cui puntano. Per questo motivo un puntatore non è autorizzato a puntare a un riferimento o a uno struct che contiene riferimenti e il tipo referenziale di un puntatore deve essere un unmanaged_type. I tipi puntatore stessi sono tipi non gestiti, pertanto un tipo puntatore può essere usato come tipo referenziale per un altro tipo di puntatore.

La regola intuitiva per la combinazione di puntatori e riferimenti è che i riferimenti di riferimenti (oggetti) sono autorizzati a contenere puntatori, ma i riferimenti dei puntatori non sono autorizzati a contenere riferimenti.

Esempio: nella tabella seguente sono riportati alcuni esempi di tipi di puntatore:

Esempio Descrizione
byte* Puntatore a byte
char* Puntatore a char
int** Puntatore al puntatore a int
int*[] Matrice unidimensionale di puntatori a int
void* Puntatore a un tipo sconosciuto

esempio finale

Per una determinata implementazione, tutti i tipi di puntatore devono avere le stesse dimensioni e rappresentazione.

Nota: a differenza di C e C++, quando più puntatori vengono dichiarati nella stessa dichiarazione, in C# * viene scritto insieme solo al tipo sottostante, non come indicatore di punteggiatura del prefisso in ogni nome del puntatore. Ad esempio:

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

nota finale

Il valore di un puntatore con tipo T* rappresenta l'indirizzo di una variabile di tipo T. L'operatore * di riferimento indiretto del puntatore (§23.6.2) può essere usato per accedere a questa variabile.

Esempio: dato una variabile P di tipo int*, l'espressione *P indica la int variabile trovata nell'indirizzo contenuto in P. esempio finale

Analogamente a un riferimento a un oggetto, un puntatore può essere null. L'applicazione dell'operatore di riferimento indiretto a un nullpuntatore con valori comporta un comportamento definito dall'implementazione (§23.6.2). Un puntatore con valore null è rappresentato da tutti i bit-zero.

Il void* tipo rappresenta un puntatore a un tipo sconosciuto. Poiché il tipo referenziale è sconosciuto, l'operatore di riferimento indiretto non può essere applicato a un puntatore di tipo void*, né può essere eseguita alcuna aritmetica su tale puntatore. Tuttavia, è possibile eseguire il cast di un puntatore di tipo void* a qualsiasi altro tipo di puntatore (e viceversa) e rispetto ai valori di altri tipi di puntatore (§23.6.8).

I tipi puntatore sono una categoria separata di tipi. A differenza dei tipi riferimento e dei tipi valore, i tipi puntatore non ereditano da object e non esistono conversioni tra tipi puntatore e object. In particolare, il boxing e l'unboxing (§8.3.13) non sono supportati per i puntatori. Tuttavia, le conversioni sono consentite tra tipi di puntatore diversi e tra i tipi puntatore e i tipi integrali. Questo è descritto in §23.5.

Un pointer_type non può essere utilizzato come argomento di tipo (§8.4) e l'inferenza del tipo (§12.6.3) non riesce nelle chiamate di metodo generiche che avrebbero dedotto un argomento di tipo come tipo puntatore.

Un pointer_type non può essere utilizzato come tipo di sottoespressione di un'operazione associata dinamicamente (§12.3.3).

Un pointer_type non può essere utilizzato come tipo del primo parametro in un metodo di estensione (§15.6.10).

Un pointer_type può essere utilizzato come tipo di campo volatile (§15.5.4).

La cancellazione dinamica di un tipo E* è il tipo di puntatore con tipo referenziale della cancellazione dinamica di E.

Non è possibile utilizzare un'espressione con un tipo di puntatore per fornire il valore in un member_declarator all'interno di un anonymous_object_creation_expression (§12.8.17.7).

Il valore predefinito (§9.3) per qualsiasi tipo di puntatore è null.

Nota: anche se i puntatori possono essere passati come parametri per riferimento, questa operazione può causare un comportamento indefinito, poiché il puntatore potrebbe essere impostato in modo da puntare a una variabile locale che non esiste più quando il metodo chiamato restituisce o l'oggetto fisso a cui puntava, non è più fisso. Ad esempio:

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

nota finale

Un metodo può restituire un valore di un tipo e tale tipo può essere un puntatore.

Esempio: quando viene assegnato un puntatore a una sequenza contigua di s, il conteggio degli intelementi della sequenza e un altro int valore, il metodo seguente restituisce l'indirizzo di tale valore in tale sequenza, se si verifica una corrispondenza; in caso contrario, restituisce 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;
}

esempio finale

In un contesto non sicuro sono disponibili diversi costrutti per l'uso su puntatori:

  • L'operatore unario * può essere utilizzato per eseguire l'indiretto del puntatore (§23.6.2).
  • L'operatore -> può essere utilizzato per accedere a un membro di uno struct tramite un puntatore (§23.6.3).
  • L'operatore [] può essere utilizzato per indicizzare un puntatore (§23.6.4).
  • L'operatore unario & può essere utilizzato per ottenere l'indirizzo di una variabile (§23.6.5).
  • Gli ++ operatori e -- possono essere utilizzati per incrementare e decrementare i puntatori (§23.6.6).
  • Gli operatori e + binari - possono essere utilizzati per eseguire l'aritmetica del puntatore (§23.6.7).
  • Gli ==operatori , !=<><=e >= possono essere usati per confrontare i puntatori (§23.6.8).
  • L'operatore stackalloc può essere usato per allocare memoria dallo stack di chiamate (§23.9).
  • L'istruzione fixed può essere usata per correggere temporaneamente una variabile in modo che possa essere ottenuto il relativo indirizzo (§23.7).

23.4 Variabili fisse e spostabili

L'operatore address-of (§23.6.5) e l'istruzione (fixed) dividono le variabili in due categorie: variabili fisse e variabili spostabili.

Le variabili fisse risiedono in posizioni di archiviazione che non sono interessate dal funzionamento del Garbage Collector. Ad esempio, le variabili fisse includono variabili locali, parametri di valore e variabili create da puntatori dereferenziali. D'altra parte, le variabili spostabili risiedono in posizioni di archiviazione soggette a rilocazione o eliminazione da parte del Garbage Collector. (Esempi di variabili spostabili includono campi in oggetti ed elementi di matrici).

L'operatore & (§23.6.5) consente di ottenere l'indirizzo di una variabile fissa senza restrizioni. Tuttavia, poiché una variabile spostabile è soggetta alla rilocazione o allo smaltimento da parte del Garbage Collector, l'indirizzo di una variabile spostabile può essere ottenuto solo usando un fixed statement (§23.7) e tale indirizzo rimane valido solo per la durata di tale fixed istruzione.

In termini precisi, una variabile fissa è una delle seguenti:

Tutte le altre variabili vengono classificate come variabili spostabili.

Un campo statico viene classificato come variabile spostabile. Inoltre, un parametro per riferimento viene classificato come variabile spostabile, anche se l'argomento specificato per il parametro è una variabile fissa. Infine, una variabile prodotta dalla dereferenziazione di un puntatore viene sempre classificata come variabile fissa.

23.5 Conversioni puntatori

23.5.1 Generale

In un contesto non sicuro, il set di conversioni implicite disponibili (§10.2) viene esteso per includere le conversioni di puntatori implicite seguenti:

  • Da qualsiasi pointer_type al tipo void*.
  • null Dal valore letterale (§6.4.5.7) a qualsiasi pointer_type.

Inoltre, in un contesto non sicuro, il set di conversioni esplicite disponibili (§10.3) viene esteso per includere le seguenti conversioni esplicite del puntatore:

  • Da qualsiasi pointer_type a qualsiasi altra pointer_type.
  • Da sbyte, byte, shortushort, int, uint, , longo ulong a qualsiasi pointer_type.
  • Da qualsiasi pointer_type a sbyte, byte, short, ushort, intuint, , longo ulong.

Infine, in un contesto non sicuro, il set di conversioni implicite standard (§10.4.2) include le conversioni di puntatori seguenti:

  • Da qualsiasi pointer_type al tipo void*.
  • null Dal valore letterale a qualsiasi pointer_type.

Le conversioni tra due tipi di puntatore non modificano mai il valore effettivo del puntatore. In altre parole, una conversione da un tipo di puntatore a un altro non ha alcun effetto sull'indirizzo sottostante specificato dal puntatore.

Quando un tipo di puntatore viene convertito in un altro, se il puntatore risultante non è allineato correttamente per il tipo puntato, il comportamento non è definito se il risultato viene dereferenziato. In generale, il concetto "correttamente allineato" è transitivo: se un puntatore al tipo A è allineato correttamente per un puntatore al tipo B, che, a sua volta, è allineato correttamente per un puntatore al tipo C, un puntatore al tipo viene allineato correttamente per un puntatore al tipo AC .

Esempio: si consideri il caso seguente in cui una variabile con un tipo è accessibile tramite un puntatore a un tipo diverso:

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
}

esempio finale

Quando un tipo di puntatore viene convertito in un puntatore a byte, il risultato punta al più basso indirizzato byte della variabile. Incrementi successivi del risultato, fino alla dimensione della variabile, restituisce puntatori ai byte rimanenti di tale variabile.

Esempio: il metodo seguente visualizza ognuno degli otto byte in un oggetto double come valore esadecimale:

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

Naturalmente, l'output prodotto dipende dalla endianità. Una possibilità è " BA FF 51 A2 90 6C 24 45".

esempio finale

I mapping tra puntatori e interi sono definiti dall'implementazione.

Nota: tuttavia, nelle architetture CPU a 32 e 64 bit con uno spazio indirizzi lineare, le conversioni di puntatori a o da tipi integrali si comportano in genere esattamente come conversioni di uint valori o ulong rispettivamente in o da tali tipi integrali. nota finale

23.5.2 Matrici di puntatori

Le matrici di puntatori possono essere costruite usando array_creation_expression (§12.8.17.5) in un contesto non sicuro. Solo alcune delle conversioni applicabili ad altri tipi di matrice sono consentite nelle matrici di puntatori:

  • La conversione implicita dei riferimenti (§10.2.8) da qualsiasi array_type a System.Array e le interfacce implementate si applicano anche alle matrici di puntatori. Tuttavia, qualsiasi tentativo di accedere agli elementi della matrice tramite System.Array o le interfacce implementate può comportare un'eccezione in fase di esecuzione, poiché i tipi di puntatore non sono convertibili in object.
  • Le conversioni di riferimento implicite ed esplicite (§10.2.8, §10.3.5) da un tipo di S[] matrice unidimensionale a System.Collections.Generic.IList<T> e le relative interfacce di base generiche non si applicano mai alle matrici di puntatori.
  • La conversione esplicita dei riferimenti (§10.3.5) da System.Array e le interfacce implementate in qualsiasi array_type si applicano alle matrici di puntatori.
  • Le conversioni di riferimento esplicite (§10.3.5) da System.Collections.Generic.IList<S> e le relative interfacce di base a un tipo di T[] matrice unidimensionale non si applicano mai alle matrici puntatore, poiché i tipi puntatore non possono essere utilizzati come argomenti di tipo e non esistono conversioni dai tipi puntatore a tipi non puntatore.

Queste restrizioni indicano che l'espansione per l'istruzione foreach sulle matrici descritte in §9.4.4.17 non può essere applicata alle matrici di puntatori. Al contrario, un'istruzione foreach del form

foreach (V v in x) embedded_statement

dove il tipo di x è un tipo di matrice del formato T[,,...,], n è il numero di dimensioni meno 1 e T o V è un tipo di puntatore, viene espanso usando cicli for annidati come segue:

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

aVariabili , i0, i1, ... in non sono visibili o accessibili a x o il embedded_statement o qualsiasi altro codice sorgente del programma. La variabile v è di sola lettura nell'istruzione incorporata. Se non è presente una conversione esplicita (§23.5) da T (tipo di elemento) a V, viene generato un errore e non vengono eseguiti altri passaggi. Se x ha il valore null, viene generata un'eccezione System.NullReferenceException in fase di esecuzione.

Nota: anche se i tipi puntatore non sono consentiti come argomenti di tipo, le matrici di puntatori possono essere usate come argomenti di tipo. nota finale

23.6 Puntatori nelle espressioni

23.6.1 Generale

In un contesto non sicuro, un'espressione può produrre un risultato di un tipo di puntatore, ma al di fuori di un contesto non sicuro, si tratta di un errore in fase di compilazione per un'espressione di un tipo puntatore. In termini precisi, all'esterno di un contesto non sicuro si verifica un errore in fase di compilazione se si verificano simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) o element_access (§12.8.12) è di un tipo puntatore.

In un contesto non sicuro, le produzioni primary_no_array_creation_expression (§12.8) e unary_expression (§12.9) consentono costrutti aggiuntivi, descritti nelle sottoclause seguenti.

Nota: la precedenza e l'associatività degli operatori non sicuri sono implicite nella grammatica. nota finale

23.6.2 Riferimento indiretto puntatore

Un pointer_indirection_expression è costituito da un asterisco (*) seguito da un unary_expression.

pointer_indirection_expression
    : '*' unary_expression
    ;

L'operatore unario * indica l'indiretto del puntatore e viene usato per ottenere la variabile a cui punta un puntatore. Il risultato della valutazione *Pdi , dove P è un'espressione di un tipo T*puntatore , è una variabile di tipo T. Si tratta di un errore in fase di compilazione per applicare l'operatore unario * a un'espressione di tipo void* o a un'espressione che non è di un tipo puntatore.

L'effetto dell'applicazione dell'operatore unario * a un nullpuntatore con valori è definito dall'implementazione. In particolare, non esiste alcuna garanzia che questa operazione generi un ' System.NullReferenceException.

Se al puntatore è stato assegnato un valore non valido, il comportamento dell'operatore unario * non è definito.

Nota: tra i valori non validi per la dereferenziazione di un puntatore da parte dell'operatore unario * è presente un indirizzo allineato in modo non appropriato per il tipo a cui punta (vedere l'esempio in §23.5) e l'indirizzo di una variabile dopo la fine della durata.

Ai fini dell'analisi dell'assegnazione definita, una variabile prodotta dalla valutazione di un'espressione del modulo *P viene considerata inizialmente assegnata (§9.4.2).

23.6.3 Accesso membro puntatore

Un pointer_member_access è costituito da un primary_expression, seguito da un token "->", seguito da un identificatore e da un type_argument_list facoltativo.

pointer_member_access
    : primary_expression '->' identifier type_argument_list?
    ;

In un membro puntatore accede al formato P->I, P deve essere un'espressione di un tipo di puntatore e I denota un membro accessibile del tipo a cui P punta.

L'accesso al membro del puntatore del modulo P->I viene valutato esattamente come (*P).I. Per una descrizione dell'operatore di riferimento indiretto del puntatore (*), vedere §23.6.2. Per una descrizione dell'operatore di accesso ai membri (.), vedere §12.8.7.

Esempio: nel codice seguente

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

L'operatore -> viene usato per accedere ai campi e richiamare un metodo di uno struct tramite un puntatore. Poiché l'operazione P->I è esattamente equivalente a (*P).I, il Main metodo potrebbe anche essere stato scritto correttamente:

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

esempio finale

23.6.4 Accesso all'elemento puntatore

Un pointer_element_access è costituito da un primary_no_array_creation_expression seguito da un'espressione racchiusa in "[" e "]".

pointer_element_access
    : primary_no_array_creation_expression '[' expression ']'
    ;

In un elemento puntatore l'accesso al formato P[E]deve essere un'espressione di un tipo di puntatore diverso da Pe void* deve essere un'espressione che può essere convertita in modo implicito in E, intuint, o long. ulong

L'accesso di un elemento puntatore del form P[E] viene valutato esattamente come *(P + E). Per una descrizione dell'operatore di riferimento indiretto del puntatore (*), vedere §23.6.2. Per una descrizione dell'operatore di addizione puntatore (+), vedere §23.6.7.

Esempio: nel codice seguente

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

Un accesso agli elementi puntatore viene usato per inizializzare il buffer dei caratteri in un for ciclo. Poiché l'operazione P[E] è esattamente equivalente a *(P + E), l'esempio potrebbe anche essere stato scritto correttamente:

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

esempio finale

L'operatore di accesso agli elementi puntatore non verifica la presenza di errori out-of-bounds e il comportamento durante l'accesso a un elemento out-of-bounds non è definito.

Nota: corrisponde a C e C++. nota finale

23.6.5 Operatore address-of

Un addressof_expression è costituito da una e commerciale (&) seguita da un unary_expression.

addressof_expression
    : '&' unary_expression
    ;

Data un'espressione di tipo E e classificata come variabile fissa (T), il costrutto calcola l'indirizzo della variabile specificata da &E.E Il tipo del risultato è T* e viene classificato come valore. Si verifica un errore in fase di compilazione se E non è classificato come variabile, se E è classificato come variabile locale di sola lettura o se E indica una variabile spostabile. Nell'ultimo caso, è possibile usare un'istruzione fissa (§23.7) per "correggere temporaneamente" la variabile prima di ottenere il relativo indirizzo.

Nota: come indicato in §12.8.7, all'esterno di un costruttore di istanza o di un costruttore statico per uno struct o una classe che definisce un readonly campo, tale campo viene considerato un valore, non una variabile. Di conseguenza, non è possibile prenderne l'indirizzo. Analogamente, non è possibile prendere l'indirizzo di una costante. nota finale

L'operatore & non richiede che l'argomento venga assegnato in modo definitivo, ma dopo un'operazione & , la variabile a cui viene applicato l'operatore viene considerata sicuramente assegnata nel percorso di esecuzione in cui si verifica l'operazione. È responsabilità del programmatore garantire che l'inizializzazione corretta della variabile avvenga effettivamente in questa situazione.

Esempio: nel codice seguente

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

i viene considerato sicuramente assegnato dopo l'operazione &i usata per inizializzare p. L'assegnazione a in *p effetti inizializza i, ma l'inclusione di questa inizializzazione è responsabilità del programmatore e non si verificherà alcun errore in fase di compilazione se l'assegnazione è stata rimossa.

esempio finale

Nota: esistono regole di assegnazione definita per l'operatore in modo che sia possibile evitare l'inizializzazione & ridondante delle variabili locali. Ad esempio, molte API esterne accettano un puntatore a una struttura compilata dall'API. Le chiamate a tali API in genere passano l'indirizzo di una variabile di struct locale e senza la regola, sarebbe necessaria l'inizializzazione ridondante della variabile struct. nota finale

Nota: quando una variabile locale, un parametro di valore o una matrice di parametri viene acquisita da una funzione anonima (§12.8.24), la variabile locale, il parametro o la matrice di parametri non viene più considerata una variabile fissa (§23.7), ma viene invece considerata una variabile spostabile. Di conseguenza, è un errore per qualsiasi codice non sicuro accettare l'indirizzo di una variabile locale, di un parametro valore o di una matrice di parametri acquisita da una funzione anonima. nota finale

23.6.6 Incremento e decremento del puntatore

In un contesto non sicuro, gli ++ operatori e -- (§12.8.16 e §12.9.6) possono essere applicati alle variabili puntatore di tutti i tipi tranne void*. Pertanto, per ogni tipo di T*puntatore , gli operatori seguenti vengono definiti in modo implicito:

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

Gli operatori producono gli stessi risultati di x+1 e x-1rispettivamente (§23.6.7). In altre parole, per una variabile puntatore di tipo T*, l'operatore ++ aggiunge sizeof(T) all'indirizzo contenuto nella variabile e l'operatore -- sottrae sizeof(T) dall'indirizzo contenuto nella variabile.

Se un'operazione di incremento o decremento del puntatore supera il dominio del tipo di puntatore, il risultato viene definito dall'implementazione, ma non vengono generate eccezioni.

23.6.7 Aritmetica del puntatore

In un contesto non sicuro, l'operatore (§12.10.5void* Pertanto, per ogni tipo di T*puntatore , gli operatori seguenti vengono definiti in modo implicito:

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

Data un'espressione P di un tipo puntatore T* e un'espressione N di tipo int, uint, longo ulong, le P + N espressioni e N + P calcolano il valore del puntatore di tipo T* risultante dall'aggiunta N * sizeof(T) all'indirizzo specificato da P. Analogamente, l'espressione P – N calcola il valore del puntatore di tipo T* risultante N * sizeof(T) dalla sottrazione dall'indirizzo specificato da P.

Dato due espressioni e PQ, di un tipo T*puntatore , l'espressione P – Q calcola la differenza tra gli indirizzi specificati da P e Q quindi divide tale differenza per sizeof(T). Il tipo del risultato è sempre long. In effetti, P - Q viene calcolato come ((long)(P) - (long)(Q)) / sizeof(T).

Esempio:

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

che produce l'output:

p - q = -14
q - p = 14

esempio finale

Se un'operazione aritmetica puntatore supera il dominio del tipo di puntatore, il risultato viene troncato in modo definito dall'implementazione, ma non vengono generate eccezioni.

Confronto puntatore 23.6.8

In un contesto non sicuro, gli ==operatori , !=<, >, <=, e >= (§12.12) possono essere applicati ai valori di tutti i tipi di puntatore. Gli operatori di confronto dei puntatori sono:

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

Poiché esiste una conversione implicita da qualsiasi tipo di puntatore al void* tipo, è possibile confrontare gli operandi di qualsiasi tipo di puntatore usando questi operatori. Gli operatori di confronto confrontano gli indirizzi specificati dai due operandi come se fossero interi senza segno.

23.6.9 Operatore sizeof

Per determinati tipi predefiniti (§12.8.19), l'operatore sizeof restituisce un valore costante int . Per tutti gli altri tipi, il risultato dell'operatore è definito dall'implementazione sizeof e viene classificato come valore, non come costante.

L'ordine in cui i membri vengono compressi in uno struct non è specificato.

Ai fini dell'allineamento, è possibile che all'inizio di uno struct, all'interno di uno struct, sia alla fine dello struct sia presente una spaziatura interna senza nome. Il contenuto dei bit utilizzati come spaziatura interna è indeterminato.

Se applicato a un operando con tipo struct, il risultato è il numero totale di byte in una variabile di tale tipo, inclusa qualsiasi spaziatura interna.

23.7 Istruzione fissa

In un contesto non sicuro, la produzione embedded_statement (§13.1) consente un costrutto aggiuntivo, l'istruzione fissa, che viene usata per "correggere" una variabile spostabile in modo che il relativo indirizzo rimanga costante per la durata dell'istruzione.

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
    ;

Ogni fixed_pointer_declarator dichiara una variabile locale del pointer_type specificato e inizializza tale variabile locale con l'indirizzo calcolato dal fixed_pointer_initializer corrispondente. Una variabile locale dichiarata in un'istruzione fissa è accessibile in qualsiasi fixed_pointer_initializerche si verifica a destra della dichiarazione di tale variabile e nella embedded_statement dell'istruzione fissa. Una variabile locale dichiarata da un'istruzione fissa è considerata di sola lettura. Si verifica un errore in fase di compilazione se l'istruzione incorporata tenta di modificare questa variabile locale (tramite assegnazione o ++ operatori e -- ) o di passarla come riferimento o parametro di output.

È un errore usare una variabile locale acquisita (§12.19.6.2), un parametro di valore o una matrice di parametri in un fixed_pointer_initializer. Un fixed_pointer_initializer può essere uno dei seguenti:

  • Il token "&" seguito da un variable_reference (§9.5) a una variabile spostabile (§23.4) di un tipo Tnon gestito, purché il tipo T* sia convertibile in modo implicito nel tipo di puntatore specificato nell'istruzione fixed . In questo caso, l'inizializzatore calcola l'indirizzo della variabile specificata e la variabile rimane in un indirizzo fisso per la durata dell'istruzione fissa.
  • Espressione di un array_type con elementi di un tipo Tnon gestito, a condizione che il tipo T* sia convertibile in modo implicito nel tipo di puntatore specificato nell'istruzione fissa. In questo caso, l'inizializzatore calcola l'indirizzo del primo elemento nella matrice e l'intera matrice rimane sempre a un indirizzo fisso per la durata dell'istruzione fixed . Se l'espressione di matrice è null o se la matrice contiene zero elementi, l'inizializzatore calcola un indirizzo uguale a zero.
  • Espressione di tipo string, a condizione che il tipo char* sia convertibile in modo implicito nel tipo di puntatore specificato nell'istruzione fixed . In questo caso, l'inizializzatore calcola l'indirizzo del primo carattere nella stringa e l'intera stringa rimane sempre in un indirizzo fisso per la durata dell'istruzione fixed . Il comportamento dell'istruzione è definito dall'implementazione fixed se l'espressione stringa è null.
  • Espressione di tipo diverso da array_type o , purché esista un metodo accessibile o un metodo di estensione accessibile corrispondente alla firma , dove string è un ref [readonly] T GetPinnableReference() ed T è convertibile in modo implicito nel tipo di puntatore specificato nell'istruzione .T*fixed In questo caso, l'inizializzatore calcola l'indirizzo della variabile restituita e tale variabile rimane in un indirizzo fisso per la durata dell'istruzione fixed . Un GetPinnableReference() metodo può essere utilizzato dall'istruzione quando la risoluzione dell'overload fixed (§12.6.4) produce esattamente un membro della funzione e tale membro della funzione soddisfa le condizioni precedenti. Il GetPinnableReference metodo deve restituire un riferimento a un indirizzo uguale a zero, ad esempio quello restituito da System.Runtime.CompilerServices.Unsafe.NullRef<T>() quando non sono presenti dati da aggiungere.
  • Un simple_name o member_access che fa riferimento a un membro del buffer a dimensione fissa di una variabile spostabile, a condizione che il tipo del membro buffer a dimensione fissa sia convertibile in modo implicito nel tipo di puntatore specificato nell'istruzione fixed . In questo caso, l'inizializzatore calcola un puntatore al primo elemento del buffer a dimensione fissa (§23.8.3) e il buffer a dimensione fissa rimane sempre in un indirizzo fisso per la durata dell'istruzione fixed .

Per ogni indirizzo calcolato da un fixed_pointer_initializer l'istruzione fixed garantisce che la variabile a cui fa riferimento l'indirizzo non sia soggetta a rilocazione o eliminazione da parte del Garbage Collector per la durata dell'istruzione fixed .

Esempio: se l'indirizzo calcolato da un fixed_pointer_initializer fa riferimento a un campo di un oggetto o a un elemento di un'istanza di matrice, l'istruzione fissa garantisce che l'istanza dell'oggetto contenitore non venga spostata o eliminata durante la durata dell'istruzione. esempio finale

È responsabilità del programmatore garantire che i puntatori creati da istruzioni fisse non superino l'esecuzione di tali istruzioni.

Esempio: quando i puntatori creati dalle fixed istruzioni vengono passati alle API esterne, è responsabilità del programmatore assicurarsi che le API non mantengano memoria di questi puntatori. esempio finale

Gli oggetti fissi possono causare la frammentazione dell'heap (perché non possono essere spostati). Per questo motivo, gli oggetti devono essere fissi solo quando assolutamente necessario e quindi solo per la quantità di tempo più breve possibile.

Esempio: esempio

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

illustra diversi usi dell'istruzione fixed . La prima istruzione corregge e ottiene l'indirizzo di un campo statico, la seconda istruzione corregge e ottiene l'indirizzo di un campo dell'istanza e la terza istruzione corregge e ottiene l'indirizzo di un elemento di matrice. In ogni caso, sarebbe stato un errore usare l'operatore regolare & poiché le variabili sono tutte classificate come variabili spostabili.

La terza e la quarta fixed istruzione nell'esempio precedente producono risultati identici. In generale, per un'istanza adi matrice , specificando a[0] in un'istruzione fixed è uguale a quello di specificare asemplicemente .

esempio finale

In un contesto non sicuro gli elementi della matrice di matrici unidimensionali vengono archiviati in ordine di indice crescente, a partire dall'indice 0 e terminando con l'indice Length – 1. Per le matrici multidimensionali, gli elementi della matrice vengono archiviati in modo che gli indici della dimensione più a destra vengano aumentati per primi, quindi la dimensione sinistra successiva e così via a sinistra.

All'interno di un'istruzione fixed che ottiene un puntatore p a un'istanza adi matrice , i valori del puntatore compresi tra p e p + a.Length - 1 rappresentano gli indirizzi degli elementi nella matrice. Analogamente, le variabili che vanno da p[0] per p[a.Length - 1] rappresentare gli elementi di matrice effettivi. Data la modalità di archiviazione delle matrici, una matrice di qualsiasi dimensione può essere considerata come se fosse lineare.

Esempio:

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

che produce l'output:

[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

esempio finale

Esempio: nel codice seguente

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

Un'istruzione fixed viene usata per correggere una matrice in modo che il relativo indirizzo possa essere passato a un metodo che accetta un puntatore.

esempio finale

Un char* valore prodotto dalla correzione di un'istanza di stringa punta sempre a una stringa con terminazione Null. All'interno di un'istruzione fissa che ottiene un puntatore p a un'istanza sdi stringa , i valori del puntatore che vanno da p a p + s.Length ‑ 1 rappresentare gli indirizzi dei caratteri nella stringa e il valore p + s.Length del puntatore punta sempre a un carattere Null (il carattere con valore '\0').

Esempio:

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

esempio finale

Esempio: il codice seguente mostra un fixed_pointer_initializer con un'espressione di tipo diverso da array_type o 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)
        {
            // ...
        }
    }
}

Il tipo C ha un metodo accessibile GetPinnableReference con la firma corretta. Nell'istruzione , l'oggetto fixedref int restituito da tale metodo quando viene chiamato su c viene usato per inizializzare il int* puntatore p. esempio finale

La modifica di oggetti di tipo gestito tramite puntatori fissi può comportare un comportamento non definito.

Nota: ad esempio, poiché le stringhe non sono modificabili, è responsabilità del programmatore assicurarsi che i caratteri a cui fa riferimento un puntatore a una stringa fissa non vengano modificati. nota finale

Nota: la terminazione automatica dei valori Null delle stringhe è particolarmente utile quando si chiamano API esterne che prevedono stringhe "in stile C". Si noti, tuttavia, che un'istanza di stringa può contenere caratteri Null. Se tali caratteri Null sono presenti, la stringa verrà troncata quando viene considerata come con terminazione char*Null. nota finale

23.8 Buffer a dimensione fissa

23.8.1 Generale

I buffer a dimensione fissa vengono usati per dichiarare matrici in linea "in stile C" come membri di struct e sono principalmente utili per l'interazione con le API non gestite.

23.8.2 Dichiarazioni di buffer a dimensione fissa

Un buffer a dimensione fissa è un membro che rappresenta l'archiviazione per un buffer a lunghezza fissa di variabili di un determinato tipo. Una dichiarazione di buffer a dimensione fissa introduce uno o più buffer a dimensione fissa di un determinato tipo di elemento.

Nota: come una matrice, un buffer a dimensione fissa può essere considerato come contenente elementi. Di conseguenza, il termine tipo di elemento definito per una matrice viene usato anche con un buffer a dimensione fissa. nota finale

I buffer a dimensione fissa sono consentiti solo nelle dichiarazioni di struct e possono verificarsi solo in contesti non sicuri (§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 ']'
    ;

Una dichiarazione di buffer a dimensione fissa può includere un set di attributi (§22), un new modificatore (§15.3.5), modificatori di accessibilità corrispondenti a qualsiasi accessibilità dichiarata consentita per i membri dello struct (§16.4.3) e un unsafe modificatore (§23.2). Gli attributi e i modificatori si applicano a tutti i membri dichiarati dalla dichiarazione di buffer a dimensione fissa. Si tratta di un errore per il quale lo stesso modificatore viene visualizzato più volte in una dichiarazione di buffer a dimensione fissa.

Una dichiarazione di buffer a dimensione fissa non può includere il static modificatore.

Il tipo di elemento buffer di una dichiarazione di buffer a dimensione fissa specifica il tipo di elemento dei buffer introdotti dalla dichiarazione. Il tipo di elemento buffer deve essere uno dei tipi sbytepredefiniti , byteshortushortintuintlongulongcharfloatdouble, o .bool

Il tipo di elemento buffer è seguito da un elenco di dichiaratori di buffer a dimensione fissa, ognuno dei quali introduce un nuovo membro. Un dichiaratore di buffer a dimensione fissa è costituito da un identificatore che denomina il membro, seguito da un'espressione costante racchiusa tra [ token e ] . L'espressione costante indica il numero di elementi nel membro introdotto dal dichiaratore di buffer a dimensione fissa. Il tipo dell'espressione costante deve essere convertibile in modo implicito nel tipo inte il valore deve essere un intero positivo diverso da zero.

Gli elementi di un buffer a dimensione fissa devono essere disposti in sequenza in memoria.

Una dichiarazione di buffer a dimensione fissa che dichiara più buffer a dimensione fissa equivale a più dichiarazioni di una singola dichiarazione di buffer a dimensione fissa con gli stessi attributi e tipi di elemento.

Esempio:

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

equivale a

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

esempio finale

23.8.3 Buffer a dimensione fissa nelle espressioni

La ricerca dei membri (§12.5) di un membro del buffer a dimensione fissa procede esattamente come la ricerca membro di un campo.

È possibile fare riferimento a un buffer a dimensione fissa in un'espressione utilizzando un simple_name (§12.8.4), un member_access (§12.8.7) o un element_access (§12.8.12).

Quando si fa riferimento a un membro del buffer a dimensione fissa come nome semplice, l'effetto corrisponde all'accesso a un membro del modulo this.I, dove I è il membro del buffer a dimensione fissa.

In un membro di accesso al modulo E.I in cui E. può essere implicito this., se E è di un tipo struct e una ricerca membro di I in tale tipo di struct identifica un membro a dimensione fissa, E.I viene valutato e classificato come segue:

  • Se l'espressione E.I non si verifica in un contesto non sicuro, si verifica un errore in fase di compilazione.
  • Se E è classificato come valore, si verifica un errore in fase di compilazione.
  • In caso contrario, se E è una variabile spostabile (§23.4) allora:
    • Se l'espressione E.I è un fixed_pointer_initializer (§23.7), il risultato dell'espressione è un puntatore al primo elemento del membro I del buffer a dimensione fissa in E.
    • In caso contrario, se l'espressione E.I è un primary_no_array_creation_expression (§12.8.12.1) all'interno di un element_access (§12.8.12) del formato E.I[J], il risultato di E.I è un puntatore, P, al primo elemento del membro I buffer a dimensione fissa in Ee il element_access contenitore viene quindi valutato come pointer_element_access (§23.6.4). P[J]
    • In caso contrario, si verifica un errore in fase di compilazione.
  • In caso contrario, E fa riferimento a una variabile fissa e il risultato dell'espressione è un puntatore al primo elemento del membro I del buffer a dimensione fissa in E. Il risultato è di tipo S*, dove S è il tipo di elemento di Ie viene classificato come valore.

È possibile accedere agli elementi successivi del buffer a dimensione fissa usando operazioni di puntatore dal primo elemento. A differenza dell'accesso alle matrici, l'accesso agli elementi di un buffer a dimensione fissa è un'operazione non sicura e non viene controllato l'intervallo.

Esempio: il codice seguente dichiara e usa uno struct con un membro del buffer a dimensione fissa.

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

esempio finale

23.8.4 Controllo delle assegnazioni definito

I buffer a dimensione fissa non sono soggetti al controllo delle assegnazioni definito (§9.4) e i membri del buffer a dimensione fissa vengono ignorati ai fini del controllo definito dei tipi di struct.

Quando la variabile struct più esterna contenente un membro del buffer a dimensione fissa è una variabile statica, una variabile di istanza di una classe o un elemento di matrice, gli elementi del buffer a dimensione fissa vengono inizializzati automaticamente sui valori predefiniti (§9.3). In tutti gli altri casi, il contenuto iniziale di un buffer a dimensione fissa non è definito.

Allocazione dello stack 23.9

Per informazioni generali sull'operatore, vedere §12.8.22 .stackalloc In questo caso, viene illustrata la capacità di tale operatore di generare un puntatore.

Quando si verifica un stackalloc_expression come espressione di inizializzazione di una local_variable_declaration (§13.6.2), dove il local_variable_type è un tipo puntatore (§23.3) o dedotto (var), il risultato dell' stackalloc_expression è un puntatore di tipo T*, dove T rappresenta il unmanaged_type dell' stackalloc_expression. In questo caso, il risultato è un puntatore all'inizio del blocco allocato.

Esempio:

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

esempio finale

A differenza dell'accesso alle matrici o stackallocai blocchi ed di Span<T> tipo , l'accesso agli elementi di un stackallocblocco ed di tipo puntatore è un'operazione non sicura e non è controllato nell'intervallo.

Esempio: nel codice seguente

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

Un'espressione stackalloc viene usata nel IntToString metodo per allocare un buffer di 16 caratteri nello stack. Il buffer viene eliminato automaticamente quando viene restituito il metodo .

Si noti, tuttavia, che IntToString può essere riscritto in modalità provvisoria, ovvero senza usare puntatori, come indicato di seguito:

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

esempio finale

Fine del testo normativo condizionale.