Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
24.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 unsafe, è possibile dichiarare e operare sui puntatori, eseguire conversioni tra puntatori dati e tipi integrali, accettare l'indirizzo di variabili e metodi 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 unsafe deve essere chiaramente contrassegnato con il modificatore
unsafe, in modo che gli sviluppatori non possano usare accidentalmente funzionalità non sicure e che il motore di esecuzione funzioni per garantire che il codice non sicuro non possa essere eseguito in un ambiente non attendibile.nota finale
24.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, una struct, un'interfaccia o un delegato può includere un
unsafemodificatore, nel qual caso l'intera estensione testuale di tale dichiarazione di tipo (incluso il corpo della classe, della struct o dell'interfaccia) è considerata 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
unsafemodificatore, nel qual caso, l'intero extent testuale della dichiarazione membro viene considerato un contesto non sicuro. - Un unsafe_statement consente l'uso di un contesto non sicuro all'interno di un blocco. L'intera estensione testuale del blocco associato è considerata 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
unsafemodificatore specificato nella dichiarazione di struct fa sì che l'intera portata testuale della dichiarazione di struct diventi un contesto unsafe. Pertanto, è possibile dichiarare i campiLefteRightcome di tipo 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
unsafemodificatori 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 sul metodo
FinAsemplicemente causa l'estensione testuale diFa diventare un contesto insicuro in cui è possibile usare le caratteristiche insicure del linguaggio. Nell'override diFinB, non è necessario specificare nuovamente ilunsafemodificatore, a meno che, naturalmente, ilFmetodo inBdebba esso stesso 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 inAo includendo ununsafemodificatore 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.
24.3 Tipi di puntatore
24.3.1 Generale
Un puntatore è una variabile in grado di contenere l'indirizzo di una variabile o di un metodo statico, detto destinazione del puntatore. Un puntatore con valore null è un puntatore Null e non punta attualmente a una variabile o a un metodo statico. L'atto di tentare di accedere alla destinazione di un puntatore è denominato dereferenziazione (§24.6.2 e §24.6.4).
In un contesto non sicuro, un tipo (§8.1) può essere un pointer_type. 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).
pointer_type
: dataptr_type
| funcptr_type
| voidptr_type
;
Il tipo della destinazione di un tipo di puntatore è denominato 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 (§24.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 o dei metodi statici 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 combinare 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.
Per una determinata implementazione, tutti i tipi di puntatore devono avere le stesse dimensioni e rappresentazione. Un valore del puntatore Null deve essere rappresentato da tutti i bit-zero.
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 §24.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).
Impossibile utilizzare un pointer_type 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.4).
Il valore predefinito (§9.3) per qualsiasi tipo di puntatore è null.
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 altrointvalore, 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
24.3.2 Puntatori dati
Un puntatore dati è un puntatore in grado di contenere l'indirizzo di una variabile con value_type (§8.3.1), funcptr_type (§24.3.3) o voidptr_type (§24.3.4).
dataptr_type
: value_type ('*')+
| funcptr_type ('*')+
| voidptr_type ('*')+
;
Un dataptr_type viene scritto come value_type che è un unmanaged_type (§8.8), funcptr_type o voidptr_type, seguito da uno o più * token.
Esempio: nella tabella seguente sono riportati alcuni esempi di tipi di puntatore dati:
Esempio Descrizione byte*Puntatore a byteint*[]Matrice unidimensionale di puntatori a intchar**Puntatore al puntatore a chardelegate*<void>*Puntatore a un puntatore a un metodo statico senza parametri e un voidtipo restituitovoid**Puntatore al puntatore a un tipo sconosciuto esempio finale
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. Per esempio:int* pi, pj; // NOT as int *pi, *pj;nota finale
Il valore non Null di un puntatore dati con tipo T* rappresenta l'indirizzo di una variabile di tipo T. L'operatore * di riferimento indiretto del puntatore (§24.6.2) può essere usato per accedere a questa variabile.
Esempio: dato una variabile
Pdi tipoint*, l'espressione*Pindica laintvariabile trovata nell'indirizzo contenuto inP. esempio finale
Nota: anche se i puntatori possono essere passati come parametri per riferimento, questa operazione con i puntatori dati può causare un comportamento indefinito, poiché il puntatore potrebbe essere impostato in modo che punti a una variabile locale che non esiste più quando il metodo chiamato restituisce o l'oggetto fisso a cui puntava, non è più fisso. Per esempio:
using System; class Test { static int value = 20; unsafe static void F(out int* pi1, ref int* pi2) { int i = 10; pi1 = &i; fixed (int* pj = &value) { // ... pi2 = pj; } } static void Main() { int i = 10; unsafe { int* px1; int* px2 = &i; F(out px1, ref px2); // Undefined behavior // Console.WriteLine($"*px1 = {*px1}, *px2 = {*px2}"); } } }nota finale
In un contesto non sicuro sono disponibili diversi costrutti per l'uso su puntatori dati:
- L'operatore unario
*può essere utilizzato per eseguire l'indiretto del puntatore (§24.6.2). - L'operatore
->può essere usato per accedere a un membro di uno struct tramite un puntatore (§24.6.3). - L'operatore
[]può essere utilizzato per indicizzare un puntatore (§24.6.4). - L'operatore unario
&può essere utilizzato per ottenere l'indirizzo di una variabile (§24.6.5). - Gli
++operatori e--possono essere utilizzati per incrementare e decrementare i puntatori (§24.6.6). - Gli operatori e
+binari-possono essere utilizzati per eseguire l'aritmetica del puntatore (§24.6.7). - Gli
==operatori ,!=<><=e>=possono essere usati per confrontare i puntatori (§24.6.8). - L'operatore
stackallocpuò essere usato per allocare memoria dallo stack di chiamate (§24.9). - L'istruzione
fixedpuò essere usata per correggere temporaneamente una variabile in modo che possa essere ottenuto il relativo indirizzo (§24.7).
24.3.3 Puntatori a funzioni
Un puntatore a funzione è un puntatore in grado di contenere l'indirizzo di un metodo statico.
funcptr_type
: 'delegate' '*' calling_convention_specifier?
'<' funcptr_parameter_list funcptr_return_type '>'
;
calling_convention_specifier
: 'managed'
| 'unmanaged' ('[' unmanaged_calling_convention ']')?
;
unmanaged_calling_convention
: 'Cdecl'
| 'Stdcall'
| 'Thiscall'
| 'Fastcall'
| identifier (',' identifier)*
;
funcptr_parameter_list
: (funcptr_parameter ',')*
;
funcptr_parameter
: parameter_mode_modifier? type
;
funcptr_return_type
: ref_kind? return_type
;
Proprio come un metodo ha una firma (§7.6), un tipo di puntatore a funzione ha una firma per il tipo di metodo a cui può puntare. Tale firma include la convenzione di chiamata.
Il valore non Null di un puntatore a funzione con tipo T rappresenta l'indirizzo di un metodo con una firma compatibile con il tipo T.
Se non viene specificata alcuna calling_convention_specifier , l'impostazione predefinita è managed, che comporta l'uso del meccanismo predefinito dell'ambiente di esecuzione. È possibile specificare convenzioni non gestite specifiche usando unmanaged_calling_convention i cui token sono mappati ai nomi definiti dall'implementazione con semantica definita dall'implementazione. Il set di combinazioni valide di questi token è definito dall'implementazione.
Nota: il calling_convention_specifier consente di scegliere un meccanismo di chiamata potenzialmente più efficiente o per i metodi scritti in linguaggi diversi da C#. nota finale.
Esempio: nella tabella seguente sono riportati alcuni esempi di tipi di puntatore a funzione:
Esempio Descrizione delegate*<void>Puntatore a un metodo gestito senza parametri e un voidtipo restituitodelegate*<void>[]Matrice di puntatori a un metodo gestito senza parametri e un voidtipo restituitodelegate*<string, string, bool>Puntatore a un metodo gestito con due stringparametri e unbooltipo restituitodelegate*<ref readonly int>Puntatore a un metodo gestito senza parametri e restituendo un ref readonly intdelegate*<delegate*<int>, void>Puntatore a un metodo gestito con un parametro che è un puntatore a un metodo senza parametri e un inttipo restituito e unvoidtipo restituitodelegate* unmanaged[Stdcall]<void>Puntatore a un metodo non gestito senza parametri e un voidtipo restituito, usando la convenzione diStdcallchiamataTenere presente quanto segue:
unsafe class Util { static void Log() { ... } static void Log(string p1) { ... } static void User() { delegate*<void>[] ary1 = new delegate*<void>[] { &Log, null }; foreach (var element in ary1) { if (element != null) { element(); // call the method being pointed to } } } }Dato che i puntatori di funzione nella matrice puntano ai metodi senza parametri,
&Logaccetta l'indirizzo delLogmetodo senza parametri. esempio finale
unmanaged_calling_convention supporta un numero ridotto di convenzioni predefinite (Cdecl, Stdcall, Thiscalle Fastcall, tutte parole chiave contestuali), che possono essere usate in modalità autonoma o come identificatore in un elenco di identificatoriunmanaged_calling_convention . Sono consentite altre convenzioni definite dall'implementazione e è possibile combinare più convenzioni usando un elenco di identificatori , possibilmente contenente una o più di queste convenzioni predefinite. La ricerca e l'elaborazione degli identificatori in questo elenco vengono eseguite in modo definito dall'implementazione.
Esempio: data una convenzione
SuppressGCTransitiondi chiamata definita dall'implementazione ,unsafe class C { delegate* unmanaged[SuppressGCTransition]<int, int> fpx; delegate* unmanaged[Stdcall, SuppressGCTransition]<int, int> fpy; }entrambi i casi usano la regola di grammatica identifier-list. esempio finale
Gli attributi personalizzati non possono essere applicati a un funcptr_type o a uno dei relativi elementi.
Un parametro di tipo funcptr_type non deve essere contrassegnato come params (§15.6.2.1).
In un contesto non sicuro sono disponibili i costrutti seguenti per l'uso su puntatori a funzione:
- L'operatore
&può essere utilizzato per ottenere l'indirizzo di un metodo statico (§24.6.5) - Gli
==operatori ,!=<><=e=>possono essere usati per confrontare i puntatori (§24.6.8). - L'operatore invocation_expression,
(), può essere utilizzato per chiamare il metodo a cui punta (§12.8.9.1).
24.3.4 Puntatori void
Un puntatore void è un puntatore in grado di contenere il valore di un puntatore dati o di un puntatore a funzione.
voidptr_type
: 'void' '*'
;
Un voidptr_type viene scritto come parola chiave void seguita dal token del tuo * .
Esempio: nella tabella seguente sono riportati alcuni esempi di tipi di puntatore void:
Esempio Descrizione void*Puntatore a un tipo sconosciuto void*[,,]Matrice tridimensionale di puntatori a un tipo sconosciuto esempio finale
Un voidptr_type 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, un puntatore di tipo void* può essere eseguito il cast a qualsiasi tipo di puntatore (e viceversa) e rispetto ai valori di altri tipi di puntatore (§24.6.8).
24.4 Variabili fisse e spostabili
L'operatore address-of (§24.6.5) e l'istruzione fixed (§24.7) dividono le variabili in due categorie: variabile fissas e variabile spostabiles.
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 dati dereferenzianti. 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 & (§24.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 (§24.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
staticfunzione non anonima (§12.22.6.2). - Variabile risultante da un member_access (§12.8.7) del formato
V.I, doveVè una variabile fissa di un struct_type. - Variabile risultante da un pointer_indirection_expression (§24.6.2) del formato
*P, un pointer_member_access (§24.6.3) del formatoP->Io un pointer_element_access (§24.6.4) del formatoP[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 dati viene sempre classificata come variabile fissa.
24.5 Conversioni puntatore
24.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*. - Da null_literal (§6.4.5.7) a qualsiasi pointer_type.
- Da funcptr_type
F0a funcptr_typeF1, purché siano soddisfatte tutte le condizioni seguenti:-
F0eF1hanno lo stesso numero di parametri e ogni parametroD0ninF0ha gli stessi modificatori di parametri per riferimento del parametroD1ncorrispondente inF1. - Per ogni parametro di valore, esiste una conversione identity, una conversione di riferimento implicita o una conversione implicita del puntatore dal tipo di parametro in
F0al tipo di parametro corrispondente inF1. - Per ogni parametro per riferimento, il tipo di parametro in
F0è uguale al tipo di parametro corrispondente inF1. - Se il tipo restituito è per valore, esiste una conversione identity, implicit reference o implicit pointer dal tipo restituito di
F1al tipo restituito diF0. - Se il tipo restituito è per riferimento, il tipo restituito e
refi modificatori diF1sono uguali al tipo restituito erefai modificatori diF0. - La convenzione di chiamata di
F0corrisponde alla convenzione di chiamata diF1.
-
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 altro pointer_type.
- Da
sbyte,byteshort,ushort,int,nintnuintuint, ,longoulonga qualsiasi pointer_type. - Da qualsiasi pointer_type a
sbyte,byteshort,ushort, ,int,uintnuintnintlongo .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*. - Da null_literal 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 dataptr_type, se il puntatore risultante non è allineato correttamente per il tipo a cui punta, 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, allora un puntatore al tipo A è allineato correttamente per un puntatore al tipo 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 all'indirizzo più basso di byte della variabile. Incrementi successivi del risultato, fino alla dimensione della variabile, restituiscono puntatori ai byte rimanenti di quella variabile.
Esempio: il metodo seguente visualizza ognuno degli otto byte in un oggetto
doublecome 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
uintvalori oulongrispettivamente in o da tali tipi integrali. nota finale
24.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.Arraye le interfacce implementate si applicano anche alle matrici di puntatori. Tuttavia, qualsiasi tentativo di accedere agli elementi della matrice tramiteSystem.Arrayo 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.Arraye dalle interfacce che implementa a qualsiasi array_type si applica 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. Invece, un'istruzione foreach della forma
foreach (V v in x)
embedded_statement
dove il tipo di x è un tipo di array della forma T[,,...,], n rappresenta il numero di dimensioni meno 1 e T o V è un tipo di puntatore. L'espansione avviene 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*
}
}
}
}
Variabili: a, i0, i1, ...
in non sono visibili o accessibili a x o al embedded_statement o a qualsiasi altro codice sorgente del programma. La variabile v è di sola lettura nell'istruzione incorporata. Se non è presente una conversione esplicita (§24.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
24.6 Puntatori nelle espressioni
24.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, al di fuori di un contesto non sicuro si verifica un errore in fase di compilazione se qualsiasi simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) o element_access (§12.8.12) è di tipo puntatore.
In un contesto non sicuro, le produzioni primary_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
Tutti gli aspetti dell'inferenza del tipo per quanto riguarda i puntatori a funzione sono descritti nelle sottoclause corrispondenti di §12.6 e §12.8.
24.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 dati. Il risultato della valutazione di *P, dove P è un'espressione di tipo puntatore T*, è una variabile di tipo T. Si tratta di un errore in fase di compilazione per applicare l'operatore unario * a un operando con tipo funcptr_type o voidptr_type.
Nota: in C/C++, un puntatore a funzione può essere dereferenziato per ottenere la funzione sottostante per chiamarla, come in
(*fp)(). Tale dereferenziazione esplicita non è consentita in C#. nota finale
L'effetto dell'applicazione dell'operatore unario * a un puntatore dati Null è definito dall'implementazione. In particolare, non esiste alcuna garanzia che questa operazione generi un System.NullReferenceException.
Se al puntatore dati è 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 dati da parte dell'operatore unario
*è presente un indirizzo allineato in modo non appropriato per il tipo a cui punta (vedere l'esempio in §24.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).
24.6.3 Accesso ai membri del 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 del puntatore di accesso al formato P->Ideve P essere un'espressione di un tipo di puntatore dati e I indica un membro accessibile del tipo a cui P punta. Si tratta di un errore in fase di compilazione per P il tipo funcptr_type o voidptr_type.
L'accesso al membro del puntatore della forma P->I viene valutato esattamente come (*P).I. Per una descrizione dell'operatore di riferimento indiretto del puntatore (*), vedere §24.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, ilMainmetodo 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
Accesso all'elemento Puntatore 24.6.4
Un pointer_element_access è costituito da un primary_expression seguito da un'espressione racchiusa in "[" e "]".
pointer_element_access
: primary_expression '[' expression ']'
;
Quando si riconosce un primary_expression se le alternative element_access e pointer_element_access (§24.6.4) sono applicabili, quest'ultimo deve essere scelto se il primary_expression incorporato è di tipo puntatore (§24.3).
In un elemento puntatore l'accesso al formato P[E]P deve essere un'espressione di un tipo di puntatore diverso da void*e E deve essere un'espressione che può essere convertita in modo implicito in int, uint, nuintnint, , longo ulong.
L'accesso di un elemento puntatore nella forma P[E] viene valutato esattamente come *(P + E). Per una descrizione dell'operatore di riferimento indiretto del puntatore (*), vedere §24.6.2. Per una descrizione dell'operatore di addizione puntatore (+), vedere §24.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
forciclo. 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 degli elementi del puntatore non esegue controlli per errori fuori dai limiti, e il comportamento quando si accede a un elemento fuori dai limiti non è definito.
Nota: corrisponde a C e C++. nota finale
24.6.5 Operatore address-of
Un addressof_expression è costituito da una e commerciale (&) seguita da un'espressione_unaria.
addressof_expression
: '&' unary_expression
;
unary_expression designerà una variabile o un gruppo di metodi. Il caso della variabile viene descritto immediatamente di seguito.
Data un'espressione E di tipo T e classificata come variabile fissa (§24.4), il costrutto &E calcola l'indirizzo della variabile specificata da 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 (§24.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
readonlycampo, 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'operando 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); } }
iviene considerato sicuramente assegnato dopo l'operazione&iusata per inizializzarep. L'assegnazione a in*peffetti 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 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 è più considerata una variabile fissa (§24.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
Il caso di unary_expression la progettazione di un gruppo di metodi viene descritto immediatamente di seguito.
In un contesto non sicuro, un metodo M è compatibile con un funcptr_typeF se sono soddisfatte tutte le condizioni seguenti:
-
MeFhanno lo stesso numero di parametri ed ogni parametro inMha gli stessi modificatoriref,outoincome il parametro corrispondente inF. - Per ogni parametro di valore, esiste una conversione identity, una conversione di riferimento implicita o una conversione implicita del puntatore dal tipo di parametro in
Mal tipo di parametro corrispondente inF. - Per ogni parametro per riferimento, il tipo di parametro in
Mè uguale al tipo di parametro corrispondente inF. - Se il tipo restituito è per valore, esiste una conversione identity, implicit reference o implicit pointer dal tipo restituito di
Fal tipo restituito diM. - Se il tipo restituito è per riferimento, il tipo restituito e
refi modificatori diFsono uguali al tipo restituito erefai modificatori diM. - La convenzione di chiamata di
Mcorrisponde alla convenzione di chiamata diF. -
Mè un metodo statico.
Esiste una conversione implicita da un unary_expression la cui destinazione è un gruppo Edi metodi , a un tipo F di puntatore a funzione compatibile se E contiene almeno un metodo applicabile nel formato normale a un elenco di argomenti costruito usando i tipi di parametro e i modificatori di F, come descritto di seguito:
- Viene selezionato un singolo metodo
Mcorrispondente a una chiamata al metodo del moduloE(A)con le modifiche seguenti:- L'elenco
Adi argomenti è un elenco di espressioni, ognuna classificata come variabile e con il tipo e il modificatore del funcptr_parameter_list corrispondente diF. - I metodi candidati sono solo i metodi applicabili nella forma normale, non quelli applicabili nel formato espanso.
- I metodi candidati sono solo i metodi statici.
- L'elenco
- Se l'algoritmo di risoluzione dell'overload genera un errore, si verifica un errore in fase di compilazione. In caso contrario, l'algoritmo produce un singolo miglior metodo
Mche ha lo stesso numero di parametri diFe la conversione viene considerata esistente. - Il metodo
Mselezionato deve essere compatibile (come definito in precedenza) con il tipo diFpuntatore a funzione . In caso contrario, si verifica un errore in fase di compilazione. - Il risultato della conversione è un puntatore a funzione di tipo
F.
Incremento e decremento del puntatore 24.6.6
In un contesto non sicuro, gli ++ operatori e -- (§12.8.16 e §12.9.7) possono essere applicati alle variabili puntatore di tutti i tipi È un errore in fase di compilazione per questi operatori da applicare alle variabili di tipo funcptr_type o voidptr_type. Di conseguenza, per ogni tipo di T*puntatore dati , gli operatori seguenti vengono definiti in modo implicito:
T* operator ++(T* x);
T* operator --(T* x);
Gli operatori producono gli stessi risultati rispettivamente di x+1 e x-1(§24.6.7). In altre parole, per una variabile del puntatore dati 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 è definito dall'implementazione e non è necessaria alcuna eccezione.
24.6.7 Aritmetica puntatore
In un contesto non sicuro, l'operatore (§12.13.5) e - l'operatore + (§12.13.6) possono essere applicati ai valori di tutti i tipi di puntatore dati. Si tratta di un errore in fase di compilazione per questi operatori da applicare a un valore di tipo funcptr_type o voidptr_type. Di conseguenza, per ogni tipo di puntatore T*, 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);
Non esistono operatori predefiniti per l'addizione o la sottrazione del puntatore con offset di interi nativi (§8.3.6). Al contrario, nint e nuint i valori devono essere promossi rispettivamente a long e ulong, con l'aritmetica del puntatore usando gli operatori predefiniti per tali tipi.
Dato un'espressione P di un tipo di T* puntatore dati e un'espressione N di tipo int, uint, longo ulong, le espressioni P + N 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 dalla sottrazione di N * sizeof(T) dall'indirizzo specificato da P.
Date due espressioni, P e Q, di un tipo T*di puntatore dati , l'espressione P – Q calcola la differenza tra gli indirizzi specificati da P e Q e 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 = 14esempio finale
Se un'operazione aritmetica puntatore supera il dominio del tipo di puntatore, il risultato viene troncato in modo definito dall'implementazione e non è necessaria alcuna eccezione.
Confronto tra puntatori 24.6.8
In un contesto non sicuro, gli ==operatori , <!=, >, <=, e >= (§12.15) possono essere applicati in modo sicuro ai valori di tutti i dataptr_typee ai valori di tutti i voidptr_types che sono copie di valori dataptr_type. 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. Tuttavia, il comportamento durante il confronto dei valori di funcptr_typeo void* delle relative copie non è definito.
Nota: in alcune piattaforme è possibile che quando l'indirizzo di un determinato metodo viene eseguito più volte, i risultati differiscono, facendo confronti con essi inaffidabili. nota finale
24.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, all'interno e alla fine di una struttura 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.
24.7 Istruzione fissa
In un contesto non sicuro, la produzione embedded_statement (§13.1) consente un costrutto aggiuntivo, l'istruzione fissa, 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.
pointer_type non deve essere funcptr_type. 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.22.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 (§24.4) di un tipoTnon gestito , purché il tipoT*sia implicitamente convertibile 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
Tnon gestito, a condizione che il tipoT*sia implicitamente convertibile 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 ènullo 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'istruzionefixedè definito dall'implementazione se l'espressione stringa ènull. - Espressione di tipo diverso da array_type o
string, purché esista un metodo accessibile o un metodo di estensione accessibile corrispondente alla firmaref [readonly] T GetPinnableReference(), doveTè un unmanaged_type edT*è convertibile in modo implicito nel tipo di puntatore specificato nell'istruzionefixed. 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 dallafixeddichiarazione quando la risoluzione dell'overload (§12.6.4) produce esattamente un membro della funzione e tale membro della funzione soddisfa le condizioni precedenti. IlGetPinnableReferencemetodo 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 un 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 (§24.8.3) e il buffer a dimensione fissa rimane 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
fixedistruzioni 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
fixedistruzione nell'esempio precedente producono risultati identici. In generale, per un'istanzaadi matrice, specificarea[0]in un'istruzionefixedè uguale a specificare semplicementea.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] = 23esempio 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
fixedviene 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 rappresentano gli indirizzi dei caratteri nella stringa e il valore p + s.Length del puntatore punta sempre a un carattere nullo (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
Cha un metodo accessibileGetPinnableReferencecon la firma corretta. Nella dichiarazione, ilfixedrestituito dal metodo quando viene chiamato suref intviene usato per inizializzare il puntatorecint*. 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 con null delle stringhe è particolarmente utile quando si chiamano API esterne che si aspettano stringhe "in stile C". Si noti, tuttavia, che un'istanza di stringa può contenere caratteri Null. Se tali caratteri nullo sono presenti, la stringa apparirà troncata quando trattata come terminata con null
char*. nota finale
24.8 Buffer a dimensione fissa
24.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.
24.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 (§24.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 (§23), 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 (§24.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 , , byte, shortushort, nintnuintlonguintintcharfloatulongdoubleo .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
24.8.3 Buffer a dimensione fissa nelle espressioni
La ricerca dei membri (§12.5) di un buffer a dimensione fissa procede esattamente come la ricerca dei membri 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.
Nell'accesso a un membro del modulo E.I in cui E. può essere l'implicito this., se E è di tipo struct e una ricerca di un membro di I in tale tipo di struct identifica un membro a dimensione fissa, allora E.I viene valutato e classificato come segue:
- Se l'espressione
E.Inon 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 (§24.4), allora:- Se l'espressione
E.Iè un fixed_pointer_initializer (§24.7), il risultato dell'espressione è un puntatore al primo elemento del membroIdel buffer a dimensione fissa inE. - In caso contrario, se l'espressione
E.Iè un primary_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 membroIbuffer a dimensione fissa inEe il element_access contenitore viene quindi valutato come pointer_element_access (§24.6.4).P[J] - In caso contrario, si verifica un errore in fase di compilazione.
- Se l'espressione
- In caso contrario,
Efa riferimento a una variabile fissa e il risultato dell'espressione è un puntatore al primo elemento del membroIdel buffer a dimensione fissa inE. Il risultato è di tipoS*, dove S è il tipo di elemento diIe 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
24.8.4 Controllo delle assegnazioni definito
I buffer a dimensione fissa non sono soggetti al controllo delle assegnazioni definite (§9.4) e i membri dei buffer a dimensione fissa sono ignorati per il controllo delle assegnazioni definite delle variabili di tipo 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 24.9
Per informazioni generali sull'operatore, vedere 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 un local_variable_declaration (§13.6.2), dove il local_variable_type è un tipo di puntatore (§24.3) o dedotto (var), il risultato del stackalloc_expression è un puntatore di tipo T*, dove T è il unmanaged_type del stackalloc_expression. In questo caso, il risultato è un puntatore all'inizio del blocco allocato.
In tutti gli altri aspetti la semantica di local_variable_declaration(§13.6.2) e stackalloc_expressions (§12.8.22) in contesti non sicuri seguono quelli definiti per contesti sicuri.
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 }; // Cannot 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 ai blocchi di tipo stackalloc, l'accesso agli elementi di un blocco di tipo puntatore Span<T> è un'operazione non sicura e non è controllato per l'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
stackallocviene usata nelIntToStringmetodo per allocare un buffer di 16 caratteri nello stack. Il buffer viene eliminato automaticamente quando viene restituito il metodo .Si noti, tuttavia, che
IntToStringpuò 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 condizionatamente normativo.
ECMA C# draft specification