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 iLeft
campi eRight
come di un tipo di puntatore. L'esempio precedente può anche essere scrittopublic 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 inA
semplicemente fa sì che l'extent testuale diF
diventi un contesto non sicuro in cui è possibile usare le funzionalità non sicure del linguaggio. Nell'override diF
inB
non è necessario specificare nuovamente ilunsafe
modificatore, a meno che, naturalmente, ilF
metodo inB
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é
F
la 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 inA
o includendo ununsafe
modificatore nella dichiarazione del metodo, come nel caso inB
.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 tipoint*
, l'espressione*P
indica laint
variabile trovata nell'indirizzo contenuto inP
. esempio finale
Analogamente a un riferimento a un oggetto, un puntatore può essere null
. L'applicazione dell'operatore di riferimento indiretto a un null
puntatore 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
int
elementi della sequenza e un altroint
valore, il metodo seguente restituisce l'indirizzo di tale valore in tale sequenza, se si verifica una corrispondenza; in caso contrario, restituiscenull
: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:
- Variabile risultante da un simple_name (§12.8.4) che fa riferimento a una variabile locale, un parametro valore o una matrice di parametri, a meno che la variabile non venga acquisita da una funzione anonima (§12.19.6.2).
- Variabile risultante da un member_access (§12.8.7
V
- Variabile risultante da un pointer_indirection_expression (§23.6.2) del formato , un pointer_member_access (
*P
) del formato o un pointer_element_access (P->I
) del formato .P[E]
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
,short
ushort
,int
,uint
, ,long
oulong
a qualsiasi pointer_type. - Da qualsiasi pointer_type a
sbyte
,byte
,short
,ushort
,int
uint
, ,long
oulong
.
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 A
C
.
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 oulong
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 tramiteSystem.Array
o le interfacce implementate può comportare un'eccezione in fase di esecuzione, poiché i tipi di puntatore non sono convertibili inobject
. - Le conversioni di riferimento implicite ed esplicite (§10.2.8, §10.3.5) da un tipo di
S[]
matrice unidimensionale aSystem.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 diT[]
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*
}
}
}
}
a
Variabili , 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 *P
di , 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 null
puntatore 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'operazioneP->I
è esattamente equivalente a(*P).I
, ilMain
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 P
e void*
deve essere un'espressione che può essere convertita in modo implicito in E
, int
uint
, 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'operazioneP[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 inizializzarep
. L'assegnazione a in*p
effetti inizializzai
, 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-1
rispettivamente (§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
, long
o 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 P
Q
, 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 tipoT
non gestito, purché il tipoT*
sia convertibile in modo implicito nel tipo di puntatore specificato nell'istruzionefixed
. 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
T
non gestito, a condizione che il tipoT*
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'istruzionefixed
. 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 tipochar*
sia convertibile in modo implicito nel tipo di puntatore specificato nell'istruzionefixed
. 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'istruzionefixed
. Il comportamento dell'istruzione è definito dall'implementazionefixed
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
è unref [readonly] T GetPinnableReference()
edT
è 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'istruzionefixed
. UnGetPinnableReference()
metodo può essere utilizzato dall'istruzione quando la risoluzione dell'overloadfixed
(§12.6.4) produce esattamente un membro della funzione e tale membro della funzione soddisfa le condizioni precedenti. IlGetPinnableReference
metodo deve restituire un riferimento a un indirizzo uguale a zero, ad esempio quello restituito daSystem.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'istruzionefixed
.
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'istanzaa
di matrice , specificandoa[0]
in un'istruzionefixed
è uguale a quello di specificarea
semplicemente .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 a
di 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 s
di 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 accessibileGetPinnableReference
con la firma corretta. Nell'istruzione , l'oggettofixed
ref int
restituito da tale metodo quando viene chiamato suc
viene usato per inizializzare ilint*
puntatorep
. 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 sbyte
predefiniti , byte
short
ushort
int
uint
long
ulong
char
float
double
, 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 int
e 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 membroI
del buffer a dimensione fissa inE
. - 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 formatoE.I[J]
, il risultato diE.I
è un puntatore,P
, al primo elemento del membroI
buffer a dimensione fissa inE
e 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.
- Se l'espressione
- In caso contrario,
E
fa riferimento a una variabile fissa e il risultato dell'espressione è un puntatore al primo elemento del membroI
del buffer a dimensione fissa inE
. Il risultato è di tipoS*
, dove S è il tipo di elemento diI
e 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 stackalloc
ai blocchi ed di Span<T>
tipo , l'accesso agli elementi di un stackalloc
blocco 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 nelIntToString
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.
ECMA C# draft specification