23 Nebezpečný kód
23.1 Obecné
K diagnostice použití syntaktických pravidel definovaných v této klauzuli se vyžaduje implementace, která nepodporuje nebezpečný kód.
Zbytek této klauzule, včetně všech jejích dílčích součástí, je podmíněně normativní.
Poznámka: Základní jazyk C# definovaný v předchozích klauzulích se liší zejména od jazyka C a C++ v jeho vynechání ukazatelů jako datového typu. Místo toho jazyk C# poskytuje odkazy a schopnost vytvářet objekty spravované uvolňováním paměti. Díky tomuto návrhu, který je ve spojení s dalšími funkcemi, je jazyk C# mnohem bezpečnější než jazyk C nebo C++. V základním jazyce C# není možné mít jednoduše neinicializovanou proměnnou, "bodový" ukazatel nebo výraz, který indexuje pole za hranicemi. Tím se eliminují celé kategorie chyb, které rutinně postihují programy C a C++.
I když prakticky každý konstruktor typu ukazatele v jazyce C nebo C++ má v jazyce C# protějšek referenčního typu, ale existují situace, kdy je přístup k typům ukazatelů nutností. Například propojení se základním operačním systémem, přístup k zařízení mapovanému v paměti nebo implementace algoritmu kritického času nemusí být možné nebo praktické bez přístupu k ukazatelům. Aby bylo možné tuto potřebu vyřešit, jazyk C# umožňuje psát nebezpečný kód.
V nebezpečném kódu je možné deklarovat a pracovat s ukazateli, provádět převody mezi ukazateli a integrálními typy, převzít adresu proměnných atd. Psaní nebezpečného kódu je v jistém smyslu podobné psaní kódu jazyka C v rámci programu jazyka C#.
Nebezpečný kód je ve skutečnosti "bezpečná" funkce z pohledu vývojářů i uživatelů. Nebezpečný kód musí být jasně označen modifikátorem
unsafe
, takže vývojáři nemohou neúmyslně používat nebezpečné funkce a spouštěcí modul funguje tak, aby se zajistilo, že nebezpečný kód nelze spustit v nedůvěryhodném prostředí.koncová poznámka
23.2 Nebezpečné kontexty
Nebezpečné funkce jazyka C# jsou dostupné jenom v nebezpečných kontextech. Nebezpečný kontext je zaveden zahrnutím unsafe
modifikátoru do deklarace typu, člena nebo místní funkce nebo použitím unsafe_statement:
- Deklarace třídy, struktury, rozhraní nebo delegáta může obsahovat
unsafe
modifikátor, v takovém případě celý textový rozsah deklarace tohoto typu (včetně těla třídy, struktury nebo rozhraní) je považován za nebezpečný kontext.Poznámka: Pokud je type_declaration částečná, je pouze tato část nebezpečným kontextem. koncová poznámka
- Deklarace pole, metody, vlastnosti, události, indexeru, operátoru, konstruktoru instance, finalizátoru, statického konstruktoru nebo místní funkce může zahrnovat
unsafe
modifikátor, v takovém případě celý textový rozsah deklarace člena je považován za nebezpečný kontext. - Unsafe_statement umožňuje použití nebezpečného kontextu v rámci bloku. Celý textový rozsah přidruženého bloku je považován za nebezpečný kontext. Místní funkce deklarovaná v rámci nebezpečného kontextu je sama o sobě nebezpečná.
Přidružená gramatická rozšíření jsou zobrazena níže a v dalších dílčích částech.
unsafe_modifier
: 'unsafe'
;
unsafe_statement
: 'unsafe' block
;
Příklad: V následujícím kódu
public unsafe struct Node { public int Value; public Node* Left; public Node* Right; }
unsafe
modifikátor zadaný v deklaraci struktury způsobí, že se celý textový rozsah deklarace struktury stane nebezpečným kontextem. Je tedy možné deklarovatLeft
pole typu ukazatele aRight
pole. Výše uvedený příklad lze také napsat.public struct Node { public int Value; public unsafe Node* Left; public unsafe Node* Right; }
Modifikátory v deklaraci polí způsobují,
unsafe
že tyto deklarace se považují za nebezpečné kontexty.end example
Kromě vytvoření nebezpečného kontextu, takže použití typů unsafe
ukazatelů nemá modifikátor žádný vliv na typ nebo člen.
Příklad: V následujícím kódu
public class A { public unsafe virtual void F() { char* p; ... } } public class B : A { public override void F() { base.F(); ... } }
nebezpečný modifikátor metody
F
jednodušeA
způsobí, že textový rozsahF
se stane nebezpečným kontextem, ve kterém lze použít nebezpečné funkce jazyka. V přepsáníF
B
není nutné modifikátor znovu specifikovatunsafe
– pokud samozřejměF
metoda samaB
nepotřebuje přístup k nebezpečným funkcím.Situace se mírně liší, když je typ ukazatele součástí podpisu metody.
public unsafe class A { public virtual void F(char* p) {...} } public class B: A { public unsafe override void F(char* p) {...} }
F
Protože podpis obsahuje typ ukazatele, může být zapsán pouze v nebezpečném kontextu. Nebezpečný kontext však lze zavést tak, že buď vytvoří celou třídu nebezpečnou, stejně jako v případěA
, nebo zahrnutímunsafe
modifikátoru v deklaraci metody, jak je tomu vB
případě .end example
Při použití modifikátoru unsafe
u částečné deklarace typu (§15.2.7) se považuje pouze za nebezpečný kontext.
23.3 Typy ukazatelů
V nebezpečném kontextu může být typ (§8.1) pointer_type i value_type, reference_type nebo type_parameter. V nebezpečném pointer_type kontextu může být také typ prvku matice (§17). Pointer_type lze použít také ve výrazu typu (§12.8.18) mimo nebezpečný kontext (například použití není nebezpečné).
Pointer_type se zapíše jako unmanaged_type (§8.8) nebo klíčové slovo void
následované tokenem*
:
pointer_type
: value_type ('*')+
| 'void' ('*')+
;
Typ zadaný před *
typem ukazatele se nazývá odkazující typ ukazatele. Představuje typ proměnné, na kterou odkazuje hodnota typu ukazatele.
Pointer_type lze použít pouze v array_type v nebezpečném kontextu (§23.2). Non_array_type je jakýkoli typ, který není sám o sobě array_type.
Na rozdíl od odkazů (hodnot referenčních typů) nejsou ukazatele sledovány uvolňováním paměti – systém uvolňování paměti nemá žádné znalosti ukazatelů a dat, na která odkazují. Z tohoto důvodu není ukazatel povolen odkazovat na odkaz nebo na strukturu, která obsahuje odkazy, a odkazující typ ukazatele musí být unmanaged_type. Samotné typy ukazatelů jsou nespravované typy, takže typ ukazatele může být použit jako typ odkazu pro jiný typ ukazatele.
Intuitivním pravidlem pro kombinování ukazatelů a odkazů je, že odkazy (objekty) mají povoleno obsahovat ukazatele, ale odkazy ukazatelů nesmí obsahovat odkazy.
Příklad: Některé příklady typů ukazatelů jsou uvedeny v následující tabulce:
Příklad Popis byte*
Ukazatel na byte
char*
Ukazatel na char
int**
Ukazatel na ukazatel na int
int*[]
Jednorozměrné pole ukazatelů na int
void*
Ukazatel na neznámý typ end example
Pro danou implementaci musí mít všechny typy ukazatelů stejnou velikost a reprezentaci.
Poznámka: Na rozdíl od jazyka C a C++ platí, že pokud je ve stejné deklaraci deklarováno více ukazatelů, zapíše se v jazyce C#
*
pouze s podkladovým typem, nikoli jako interpunkční znaménou předponu pro každý název ukazatele. Příklad:int* pi, pj; // NOT as int *pi, *pj;
koncová poznámka
Hodnota ukazatele s typem T*
představuje adresu proměnné typu T
. Operátor nepřímých *
ukazatelů (§23.6.2) lze použít pro přístup k této proměnné.
Příklad: Při zadání proměnné
P
typuint*
výraz*P
označuje proměnnou nalezenouint
na adrese obsažené vP
. end example
Podobně jako odkaz na objekt může být null
ukazatel . Použití operátoru indirection na null
ukazatel -valued má za následek chování definované implementací (§23.6.2). Ukazatel s hodnotou null
je reprezentován all-bits-zero.
Typ void*
představuje ukazatel na neznámý typ. Vzhledem k tomu, že odkazující typ není znám, nelze operátor nepřímých výrazů použít u ukazatele typu void*
, ani nelze u tohoto ukazatele provádět žádné aritmetické operace. Ukazatel typu void*
však lze přetypovat na jakýkoli jiný typ ukazatele (a naopak) a porovnat s hodnotami jiných typů ukazatele (§23.6.8).
Typy ukazatelů jsou samostatnou kategorií typů. Na rozdíl od odkazových typů a typů hodnot typy ukazatelů nedědí object
a neexistují žádné převody mezi typy ukazatelů a object
. Zejména pro ukazatele nejsou podporovány boxování a rozbalování (§8.3.13). Převody jsou však povoleny mezi různými typy ukazatelů a mezi typy ukazatelů a integrálními typy. Toto je popsáno v §23.5.
Pointer_type nelze použít jako argument typu (§8.4) a odvození typu (§12.6.3) se nezdaří u volání obecných metod, která by odvozovala typ argumentu typu.
Pointer_type nelze použít jako typ dílčího výrazu dynamicky vázané operace (§12.3.3).
Pointer_type nelze použít jako typ prvního parametru v metodě rozšíření (§15.6.10).
Pointer_type lze použít jako typ nestálého pole (§15.5.4).
Dynamické vymazání typu E*
je typ ukazatele s odkazovým typem dynamického E
vymazání .
Výraz s typem ukazatele nelze použít k zadání hodnoty v member_declarator v rámci anonymous_object_creation_expression (§12.8.17.7).
Výchozí hodnota (§9.3) pro jakýkoli typ ukazatele je null
.
Poznámka: I když lze ukazatele předat jako parametry odkazu, může to způsobit nedefinované chování, protože ukazatel může být správně nastaven tak, aby odkazoval na místní proměnnou, která již neexistuje, když volá metoda vrací, nebo pevný objekt, na který byl použit k bodu, již není opraven. Příklad:
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 } } }
koncová poznámka
Metoda může vrátit hodnotu určitého typu a tento typ může být ukazatel.
Příklad: Při zadání ukazatele na souvislou sekvenci sekvencí
int
prvků této sekvence a některé dalšíint
hodnoty vrátí následující metoda adresu této hodnoty v této sekvenci, pokud dojde ke shodě; jinak vrátínull
:unsafe static int* Find(int* pi, int size, int value) { for (int i = 0; i < size; ++i) { if (*pi == value) { return pi; } ++pi; } return null; }
end example
V nebezpečném kontextu je k dispozici několik konstruktorů pro práci s ukazateli:
- Unární
*
operátor lze použít k provedení nepřímých ukazatelů (§23.6.2). - Operátor
->
lze použít pro přístup ke členu struktury ukazatelem (§23.6.3). - Operátor
[]
lze použít k indexování ukazatele (§23.6.4). - Unární
&
operátor lze použít k získání adresy proměnné (§23.6.5). - Operátory
++
mohou--
být použity k inkrementování a dekrementování ukazatelů (§23.6.6). -
+
Binární a-
operátory lze použít k aritmetické aritmetice ukazatele (§23.6.7). - Operátory
==
, ,!=
,<
>
<=
a>=
mohou být použity k porovnání ukazatelů (§23.6.8). - Operátor
stackalloc
lze použít k přidělení paměti ze zásobníku volání (§23.9). - Příkaz
fixed
lze použít k dočasné opravě proměnné, aby bylo možné získat její adresu (§23.7).
23.4 Pevné a pohyblivé proměnné
Operátor adresy (§23.6.5) a výrok (fixed
) rozdělují proměnné do dvou kategorií: Pevné proměnné a pohyblivé proměnné.
Pevné proměnné se nacházejí v umístěních úložiště, na která nemá vliv operace uvolňování paměti. (Mezi příklady pevných proměnných patří místní proměnné, parametry hodnot a proměnné vytvořené ukazateli dereferencingu.) Na druhé straně se pohyblivé proměnné nacházejí v umístěních úložiště, která podléhají přemístění nebo odstranění uvolňováním paměti. (Příklady přesunoutelných proměnných zahrnují pole v objektech a prvcích polí.)
Operátor &
(§23.6.5) umožňuje získat adresu pevné proměnné bez omezení. Vzhledem k tomu, že pohyblivá proměnná podléhá přemístění nebo odstranění odpadkovým kolektorem, lze adresu přesunoutelné proměnné získat pouze pomocí bodu fixed statement
23.7 a tato adresa zůstává platná pouze po dobu trvání tohoto fixed
prohlášení.
Pevná proměnná je přesně jedna z následujících možností:
- Proměnná vyplývající z simple_name (§12.8.4), která odkazuje na místní proměnnou, parametr hodnoty nebo pole parametrů, pokud není proměnná zachycena anonymní funkcí (§12.19.6.2).
- Proměnná vyplývající z member_access (§12.8.7
V
- Proměnná vyplývající z pointer_indirection_expression (§23.6.2) formuláře
*P
, pointer_member_access (§23.6.3) formulářeP->I
nebo pointer_element_access (§23.6.4) formulářeP[E]
.
Všechny ostatní proměnné jsou klasifikovány jako pohyblivé proměnné.
Statické pole je klasifikováno jako přesunoutelná proměnná. Parametr by-reference je také klasifikován jako přesunoutelná proměnná, i když argument zadaný parametrem je pevná proměnná. Nakonec je proměnná vytvořená dereferencováním ukazatele vždy klasifikována jako pevná proměnná.
23.5 Převody ukazatelů
23.5.1 Obecné
V nezabezpečeném kontextu je sada dostupných implicitních převodů (§10.2) rozšířena tak, aby zahrnovala následující implicitní převody ukazatelů:
- Z libovolného pointer_type na typ
void*
. - Z literálu
null
(§6.4.5.7) do libovolného pointer_type.
Kromě toho je v nezabezpečeném kontextu rozšířena sada dostupných explicitních převodů (§10.3), aby zahrnovala následující explicitní převody ukazatelů:
- Z libovolného pointer_type do jakéhokoli jiného pointer_type.
- Od
sbyte
,byte
, ,short
,ushort
, ,int
,uint
,long
neboulong
do libovolné pointer_type. - Z libovolného pointer_type do
sbyte
,byte
,short
ushort
int
uint
,long
, nebo .ulong
V nezabezpečeném kontextu obsahuje sada standardních implicitních převodů (§10.4.2) následující převody ukazatelů:
- Z libovolného pointer_type na typ
void*
. - Z literálu
null
do libovolného pointer_type.
Převody mezi dvěma typy ukazatelů nikdy nemění skutečnou hodnotu ukazatele. Jinými slovy, převod z jednoho typu ukazatele na jiný nemá žádný vliv na podkladovou adresu danou ukazatelem.
Pokud je jeden typ ukazatele převeden na jiný, pokud výsledný ukazatel není správně zarovnaný pro odkaz na typ, chování není definováno, pokud je výsledek dereferenced. Obecně platí, že koncept "správně zarovnaný" je tranzitivní: pokud je ukazatel na typ A
správně zarovnaný pro ukazatel B
, který je zase správně zarovnán pro typ ukazatele C
, pak je ukazatel na typ A
správně zarovnán pro ukazatel k typu C
.
Příklad: Představte si následující případ, kdy se k proměnné s jedním typem přistupuje pomocí ukazatele na jiný typ:
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 }
end example
Pokud je typ ukazatele převeden na ukazatel na byte
, výsledek odkazuje na nejnižší adresovanou byte
proměnnou. Následné přírůstky výsledku až do velikosti proměnné poskytují ukazatele na zbývající bajty této proměnné.
Příklad: Následující metoda zobrazí každou z osmi bajtů v
double
šestnáctkové hodnotě: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(); } } }
Výstup samozřejmě závisí na endianitě. Jednou z možností je
" BA FF 51 A2 90 6C 24 45"
.end example
Mapování mezi ukazateli a celými čísly jsou definována implementací.
Poznámka: U 32bitových a 64bitových architektur procesoru s lineárním adresní prostorem se převody ukazatelů na celočíselné typy obvykle chovají přesně stejně jako převody
uint
neboulong
hodnoty nebo z těchto integrálních typů. koncová poznámka
23.5.2 Pole ukazatelů
Pole ukazatelů lze vytvořit pomocí array_creation_expression (§12.8.17.5) v nebezpečném kontextu. U polí ukazatelů jsou povoleny pouze některé převody, které platí pro jiné typy polí:
- Implicitní převod odkazu (§10.2.8) z libovolného array_type do
System.Array
a rozhraní, která implementuje, platí také pro pole ukazatelů. Jakýkoli pokus o přístup k prvkům pole prostřednictvímSystem.Array
nebo rozhraní, která implementuje, může vést k výjimce za běhu, protože typy ukazatelů nejsou konvertibilní naobject
. - Implicitní a explicitní odkazové převody (§10.2.8, §10.3.5) z jednorozměrného typu
S[]
pole naSystem.Collections.Generic.IList<T>
obecné základní rozhraní se nikdy nevztahují na pole ukazatelů. - Explicitní převod odkazu (§10.3.5) z
System.Array
a rozhraní, která implementuje, na jakákoli array_type se vztahuje na pole ukazatelů. - Explicitní převody odkazů (§10.3.5) a
System.Collections.Generic.IList<S>
jeho základní rozhraní na jednorozměrný typT[]
pole nikdy neplatí pro pole ukazatelů, protože typy ukazatelů nelze použít jako argumenty typu a neexistují žádné převody z typů ukazatelů na jiné typy než ukazatele.
Tato omezení znamenají, že rozšíření foreach
příkazu nad maticemi popsanými v §9.4.4.17 nelze použít pro pole ukazatelů. Místo toho příkaz foreach
formuláře
foreach (V v in x)
embedded_statement
kde typ x
je typ pole formuláře T[,,...,]
, n je počet dimenzí minus 1 a T
nebo V
je typ ukazatele, je rozšířen pomocí vnořených for-loops následujícím způsobem:
{
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*
}
}
}
}
Proměnné a
, i0
, i1
...
in
nejsou viditelné ani přístupné x
pro embedded_statement ani pro žádný jiný zdrojový kód programu. Proměnná v
je v vloženém příkazu jen pro čtení. Pokud neexistuje explicitní převod (§23.5) z T
(typu prvku) na , dojde k V
chybě a neprovedou se žádné další kroky. Pokud x
má hodnotu null
, System.NullReferenceException
vyvolá se za běhu.
Poznámka: I když nejsou typy ukazatelů povoleny jako argumenty typu, lze pole ukazatelů použít jako argumenty typu. koncová poznámka
23.6 Ukazatele ve výrazech
23.6.1 Obecné
V nebezpečném kontextu může výraz přinést výsledek typu ukazatele, ale mimo nebezpečný kontext, jedná se o chybu v době kompilace pro výraz typu ukazatele. Z přesného hlediska dojde mimo nebezpečný kontext k chybě v době kompilace, pokud je některá simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) nebo element_access (§12.8.12) typu ukazatele.
V nezabezpečeném kontextu primary_no_array_creation_expression (§12.8) a unary_expression (§12.9) produkce umožňují další konstrukce, které jsou popsány v následujících dílčích nákládách.
Poznámka: Priorita a asociativita nebezpečných operátorů je odvozena gramatikou. koncová poznámka
23.6.2 Nepřímý ukazatel
Pointer_indirection_expression se skládá z hvězdičky (*
) následované unary_expression.
pointer_indirection_expression
: '*' unary_expression
;
Unární *
operátor označuje nepřímý ukazatel a slouží k získání proměnné, na kterou ukazatel odkazuje. Výsledek vyhodnocení *P
, kde P
je výraz typu T*
ukazatele , je proměnná typu T
. Jedná se o chybu v době kompilace, která použije unární *
operátor na výraz typu void*
nebo na výraz, který není typu ukazatele.
Účinek použití unárního *
operátoru na null
ukazatel -valued je definován implementací. Konkrétně neexistuje žádná záruka, že tato operace vyvolá System.NullReferenceException
.
Pokud byla ukazateli přiřazena neplatná hodnota, chování unárního *
operátoru není definováno.
Poznámka: Mezi neplatnými hodnotami pro zrušení odvozování ukazatele unárním
*
operátorem je adresa nesprávně zarovnaná pro typ, na který odkazuje (viz příklad v §23.5) a adresa proměnné po konci jeho životnosti.
Pro účely určité analýzy přiřazení je proměnná vytvořená vyhodnocením výrazu formuláře *P
považována za původně přiřazenou (§9.4.2).
23.6.3 Přístup ke členu ukazatele
Pointer_member_access se skládá z primary_expression a za ním token "->
" následovaný identifikátorem a volitelným type_argument_list.
pointer_member_access
: primary_expression '->' identifier type_argument_list?
;
V přístupu ke členu ukazatele formuláře P->I
P
musí být výraz typu ukazatele a I
označuje přístupný člen typu, na který P
odkazuje.
Přístup člena ukazatele formuláře P->I
je vyhodnocen přesně jako (*P).I
. Popis operátoru nepřímého ukazatele (*
), viz §23.6.2. Popis operátora přístupu člena (.
), viz §12.8.7.
Příklad: V následujícím kódu
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()); } } }
operátor
->
slouží k přístupu k polím a vyvolá metodu struktury prostřednictvím ukazatele. Vzhledem k tomu, že operaceP->I
je přesně ekvivalentní(*P).I
,Main
metoda by mohla být stejně dobře napsána:class Test { static void Main() { Point point; unsafe { Point* p = &point; (*p).x = 10; (*p).y = 20; Console.WriteLine((*p).ToString()); } } }
end example
Přístup k prvku ukazatele 23.6.4
Pointer_element_access se skládá z primary_no_array_creation_expression následovaného výrazem uzavřeným v "[
" a "]
".
pointer_element_access
: primary_no_array_creation_expression '[' expression ']'
;
V přístupu prvku ukazatele formuláře P[E]
P
musí být výraz jiného typu ukazatele než void*
a E
musí být výrazem, který lze implicitně převést na int
, uint
, nebo long
ulong
.
Přístup prvku ukazatele formuláře P[E]
je vyhodnocen přesně jako *(P + E)
. Popis operátoru nepřímého ukazatele (*
), viz §23.6.2. Popis operátoru sčítání ukazatele (+
), viz §23.6.7.
Příklad: V následujícím kódu
class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { p[i] = (char)i; } } } }
Přístup k elementu ukazatele slouží k inicializaci vyrovnávací paměti znaků ve smyčce
for
. Vzhledem k tomu, že operaceP[E]
je přesně ekvivalentní*(P + E)
, mohl by být příklad stejně dobře napsán:class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { *(p + i) = (char)i; } } } }
end example
Přístupový operátor prvku ukazatele nekontroluje chyby mimo hranice a chování při přístupu k prvku mimo hranice není definováno.
Poznámka: Toto je stejné jako C a C++. koncová poznámka
23.6.5 Operátor adresy
Addressof_expression se skládá z ampersandu (&
) následovaného unary_expression.
addressof_expression
: '&' unary_expression
;
Vzhledem k výrazu E
, který je typu T
a je klasifikován jako pevná proměnná (§23.4), konstrukce &E
vypočítá adresu proměnné dané E
. Typ výsledku je T*
a je klasifikován jako hodnota. K chybě v době kompilace dochází, pokud E
není klasifikována jako proměnná, pokud E
je klasifikována jako místní proměnná jen pro čtení nebo pokud E
označuje přesunoutelnou proměnnou. V posledním případě lze k dočasnému "opravě" proměnné použít pevný příkaz (§23.7).
Poznámka: Jak je uvedeno v §12.8.7, mimo konstruktor instance nebo statický konstruktor pro strukturu nebo třídu, která definuje
readonly
pole, je toto pole považováno za hodnotu, nikoli proměnnou. Proto jeho adresu nelze přijmout. Podobně nelze vzít adresu konstanty. koncová poznámka
Operátor &
nevyžaduje, aby byl jeho argument rozhodně přiřazen, ale po &
operaci se proměnná, ke které je operátor použit, považuje za rozhodně přiřazenou v cestě provádění, ve které k operaci dochází. Je zodpovědností programátora zajistit, aby se v této situaci skutečně uskutečnila správná inicializace proměnné.
Příklad: V následujícím kódu
class Test { static void Main() { int i; unsafe { int* p = &i; *p = 123; } Console.WriteLine(i); } }
i
se považuje za rozhodně přiřazené po&i
operaci použité k inicializacip
. Přiřazení, které*p
má být inicializovánoi
, ale zahrnutí této inicializace je odpovědnost programátora a při odebrání přiřazení nedojde k žádné chybě v době kompilace.end example
Poznámka: Pravidla určitého přiřazení operátoru
&
existují tak, aby se zabránilo redundantní inicializaci místních proměnných. Mnoho externích rozhraní API například vezme ukazatel na strukturu, která je vyplněna rozhraním API. Volání těchto rozhraní API obvykle předávají adresu místní proměnné struktury a bez pravidla bude vyžadována redundantní inicializace proměnné struktury. koncová poznámka
Poznámka: Pokud je místní proměnná, parametr hodnoty nebo pole parametrů zachycena anonymní funkcí (§12.8.24), tato místní proměnná, parametr nebo pole parametrů se již nepovažuje za pevnou proměnnou (§23.7), ale je považována za pohyblivou proměnnou. Proto se jedná o chybu, že jakýkoli nebezpečný kód vezme adresu místní proměnné, parametru hodnoty nebo pole parametrů zachycené anonymní funkcí. koncová poznámka
23.6.6 Přírůstek ukazatele a dekrementace
V nezabezpečeném kontextu ++
lze operátory a --
operátory (§12.8.16 a §12.9.6) použít u proměnných ukazatelů všech typů s výjimkou void*
. Proto pro každý typ T*
ukazatele jsou implicitně definovány následující operátory:
T* operator ++(T* x);
T* operator --(T* x);
Provozovatelé mají stejné výsledky jako x+1
a x-1
v uvedeném pořadí (§23.6.7). Jinými slovy, pro proměnnou ukazatele typu T*
se operátor přidá ++
k adrese obsažené v proměnné a sizeof(T)
operátor odečte --
od adresy sizeof(T)
obsažené v proměnné.
Pokud operace inkrementace nebo dekrementace ukazatele přeteče doménu typu ukazatele, výsledek je definovaný implementací, ale nevygenerují se žádné výjimky.
23.6.7 Aritmetika ukazatele
V nezabezpečeném kontextu +
lze operátor (§12.10.5) a -
operátor (§12.10.6) použít na hodnoty všech typů ukazatelů s výjimkou void*
. Proto pro každý typ T*
ukazatele jsou implicitně definovány následující operátory:
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);
Vzhledem k výrazu P
typu ukazatele a výrazu T*
typu N
int
, uint
, long
nebo ulong
, výrazy P + N
a N + P
vypočítat hodnotu ukazatele typuT*
, která je výsledkem přidání N * sizeof(T)
na adresu danou P
. Stejně tak výraz P – N
vypočítá hodnotu ukazatele typu T*
, která má za následek odečtení N * sizeof(T)
od adresy zadané adresou P
.
Vzhledem k dvěma výrazům P
a Q
, typu T*
ukazatele výraz P – Q
vypočítá rozdíl mezi adresami zadanými P
a Q
potom tento rozdíl sizeof(T)
vydělí . Typ výsledku je vždy long
. V důsledku toho se P - Q
vypočítá jako ((long)(P) - (long)(Q)) / sizeof(T)
.
Příklad:
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}"); } } }
který vytvoří výstup:
p - q = -14 q - p = 14
end example
Pokud aritmetická operace ukazatele přeteče doménu typu ukazatele, výsledek se zkrátí způsobem definovaným implementací, ale nevygenerují se žádné výjimky.
23.6.8 Porovnání ukazatelů
V nezabezpečeném kontextu ==
lze použít operátory , , !=
<
>
, <=
a >=
operátory (§12.12) na hodnoty všech typů ukazatele. Relační operátory ukazatelů jsou:
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);
Vzhledem k tomu, že implicitní převod existuje z libovolného typu ukazatele na void*
typ, lze operandy libovolného typu ukazatele porovnat pomocí těchto operátorů. Operátory porovnání porovnávají adresy zadané dvěma operandy, jako by byly celá čísla bez znaménka.
23.6.9 Operátor sizeof
U některých předdefinovaných typů (§12.8.19) sizeof
operátor poskytuje konstantní int
hodnotu. U všech ostatních typů je výsledek operátoru sizeof
definovaný implementací a je klasifikován jako hodnota, nikoli konstanta.
Pořadí, ve kterém jsou členy zabaleny do struktury, není zadáno.
Pro účely zarovnání může na začátku struktury, v rámci struktury a na konci struktury existovat nepojmenované odsazení. Obsah bitů použitých jako odsazení je neurčitý.
Při použití na operand, který má typ struktury, je výsledkem celkový počet bajtů v proměnné tohoto typu, včetně jakéhokoli odsazení.
23.7 Pevný příkaz
V nezabezpečeném kontextu umožňuje výroba embedded_statement (§13.1) další konstruktor, pevný příkaz, který se používá k "opravě" pohyblivé proměnné tak, aby jeho adresa zůstala konstantní po dobu trvání příkazu.
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
;
Každá fixed_pointer_declarator deklaruje místní proměnnou daného pointer_type a inicializuje ji s adresou vypočítanou odpovídající fixed_pointer_initializer. Místní proměnná deklarovaná v pevném příkazu je přístupná v libovolném fixed_pointer_initializer, ke kterým dochází napravo od deklarace této proměnné, a v embedded_statement pevného příkazu. Místní proměnná deklarovaná pevným příkazem je považována za jen pro čtení. K chybě v době kompilace dojde, pokud se vložený příkaz pokusí upravit tuto místní proměnnou (prostřednictvím přiřazení nebo ++
--
operátorů) nebo ji předat jako odkaz nebo výstupní parametr.
Jedná se o chybu použití zachycené místní proměnné (§12.19.6.2), parametru hodnoty nebo pole parametrů v fixed_pointer_initializer. Fixed_pointer_initializer může být jedna z následujících možností:
- Token "
&
" následovaný variable_reference (§9.5) do pohyblivé proměnné (§23.4) nespravovaného typuT
za předpokladu, že typT*
je implicitně konvertibilní na typ ukazatele zadaný vfixed
příkazu. V tomto případě inicializátor vypočítá adresu dané proměnné a proměnná je zaručena, že zůstane na pevné adrese po dobu trvání pevného příkazu. - Výraz array_type s prvky nespravovaného typu
T
, za předpokladu, že typT*
je implicitně převoditelný na typ ukazatele zadaný v pevném příkazu. V tomto případě inicializátor vypočítá adresu prvního prvku v poli a celé pole je zaručeno, že zůstane na pevné adrese po dobu trvánífixed
příkazu. Pokud jenull
maticový výraz nebo pokud má matice nula prvků, inicializátor vypočítá adresu rovnou nule. - Výraz typu
string
, za předpokladu, že typchar*
je implicitně konvertibilní na typ ukazatele zadaný vfixed
příkazu. V tomto případě inicializátor vypočítá adresu prvního znaku v řetězci a celý řetězec je zaručen, že zůstane na pevné adrese po dobu trvánífixed
příkazu. Chovánífixed
příkazu je definováno implementací, pokud jenull
řetězcový výraz . - Výraz jiného typu než array_type nebo
string
za předpokladu, že existuje přístupná metoda nebo přístupná rozšiřující metoda odpovídající podpisuref [readonly] T GetPinnableReference()
, kdeT
je unmanaged_type aT*
implicitně se konvertibilní na typ ukazatele zadaný vfixed
příkazu. V tomto případě inicializátor vypočítá adresu vrácené proměnné a tato proměnná je zaručena, že zůstane na pevné adrese po dobu trvánífixed
příkazu. MetoduGetPinnableReference()
lze použít příkazemfixed
při rozlišení přetížení (§12.6.4) vytvoří přesně jeden člen funkce a tento člen funkce splňuje předchozí podmínky. MetodaGetPinnableReference
by měla vrátit odkaz na adresu rovnající se nule, jako je ta vrácená,System.Runtime.CompilerServices.Unsafe.NullRef<T>()
když neexistují žádná data pro připnutí. - Simple_name nebo member_access, které odkazují na člen vyrovnávací paměti s pevnou velikostí pohyblivé proměnné za předpokladu, že typ členu vyrovnávací paměti s pevnou velikostí je implicitně převést na typ ukazatele zadaný v
fixed
příkazu. V tomto případě inicializátor vypočítá ukazatel na první prvek vyrovnávací paměti s pevnou velikostí (§23.8.3) a vyrovnávací paměť s pevnou velikostí je zaručena zůstat na pevné adrese po dobu trvánífixed
příkazu.
Pro každou adresu vypočítanou fixed_pointer_initializerfixed
příkaz zajistí, aby proměnná odkazovaná adresou nebyla předmětem přemístění nebo odstranění uvolňováním paměti po dobu trvání fixed
příkazu.
Příklad: Pokud adresa vypočítaná fixed_pointer_initializer odkazuje na pole objektu nebo prvku instance pole, pevný příkaz zaručuje, že obsahující instance objektu není přesunuta nebo uvolněna během životnosti příkazu. end example
Je zodpovědností programátora zajistit, aby ukazatele vytvořené pevnými příkazy nepřežily nad rámec provádění těchto příkazů.
Příklad: Když jsou ukazatele vytvořené příkazy
fixed
předány externím rozhraním API, je zodpovědností programátora zajistit, aby rozhraní API nezachovávají žádnou paměť těchto ukazatelů. end example
Pevné objekty můžou způsobit fragmentaci haldy (protože se nedají přesunout). Z tohoto důvodu by objekty měly být opraveny pouze v případě, že je to nezbytně nutné, a pak pouze na nejkratší možnou dobu.
Příklad: Příklad
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); } } }
ukazuje několik použití
fixed
příkazu. První příkaz opraví a získá adresu statického pole, druhý příkaz opraví a získá adresu pole instance a třetí příkaz opraví a získá adresu prvku pole pole. V každém případě by se při použití běžného&
operátoru stala chyba, protože proměnné jsou klasifikovány jako pohyblivé proměnné.Třetí a čtvrté
fixed
příkazy v příkladu výše vytvářejí identické výsledky. Obecně platí, že pro instancia
pole , určenía[0]
vfixed
příkazu je stejné jako jednoduše zadata
.end example
V nezabezpečeném kontextu jsou prvky pole jednorozměrných polí uloženy ve vzestupném pořadí indexu, počínaje indexem 0
a končícím indexem Length – 1
. U multidimenzionálních polí jsou prvky matice uloženy tak, aby indexy pravé dimenze byly nejprve zvýšeny, pak další levá dimenze a tak dále nalevo.
fixed
V rámci příkazu, který získá ukazatel na instanci p
pole , hodnoty ukazatele a
od p
toho, aby p + a.Length - 1
představovaly adresy prvků v matici. Podobně proměnné v rozsahu od p[0]
toho, aby p[a.Length - 1]
představovaly skutečné prvky pole. Vzhledem k tomu, jakým způsobem jsou pole uložena, lze pole libovolné dimenze považovat za lineární.
Příklad:
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(); } } } }
který vytvoří výstup:
[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
end example
Příklad: V následujícím kódu
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); } } }
Příkaz
fixed
slouží k opravě pole, aby jeho adresa byla předána metodě, která přebírá ukazatel.end example
char*
Hodnota vytvořená opravou instance řetězce vždy odkazuje na řetězec ukončený hodnotou null. V rámci pevného příkazu, který získá ukazatel na instanci p
řetězce , hodnoty ukazatele s
v rozsahu od p
p + s.Length ‑ 1
reprezentace adres znaků v řetězci a hodnota p + s.Length
ukazatele vždy odkazuje na znak null (znak s hodnotou \0).
Příklad:
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); } } }
end example
Příklad: Následující kód ukazuje fixed_pointer_initializer s výrazem jiného typu než array_type nebo
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) { // ... } } }
Typ
C
má přístupnouGetPinnableReference
metodu se správným podpisem.fixed
V příkazu,ref int
vrácený z této metody, když je volánac
je použita k inicializaciint*
ukazatelep
. end example
Úprava objektů spravovaného typu prostřednictvím pevných ukazatelů může způsobit nedefinované chování.
Poznámka: Například vzhledem k tomu, že řetězce jsou neměnné, je zodpovědností programátora zajistit, aby se znaky odkazované ukazatelem na pevný řetězec nezměnily. koncová poznámka
Poznámka: Automatické ukončení řetězců s hodnotou null je obzvláště vhodné při volání externích rozhraní API, která očekávají řetězce ve stylu jazyka C. Upozorňujeme však, že instance řetězce může obsahovat znaky null. Pokud jsou tyto znaky null přítomny, řetězec se při zacházení s ukončením null zobrazí zkráceně
char*
. koncová poznámka
23.8 Vyrovnávací paměti s pevnou velikostí
23.8.1 Obecné
Vyrovnávací paměti s pevnou velikostí se používají k deklaraci "C-style" v řádcích polí jako členů struktur a jsou primárně užitečné pro propojení s nespravovanými rozhraními API.
23.8.2 Deklarace vyrovnávací paměti s pevnou velikostí
Vyrovnávací paměť s pevnou velikostí je člen, který představuje úložiště pro vyrovnávací paměť s pevnou délkou proměnných daného typu. Deklarace vyrovnávací paměti s pevnou velikostí zavádí jednu nebo více vyrovnávacích pamětí s pevnou velikostí daného typu prvku.
Poznámka: Stejně jako pole lze vyrovnávací paměť s pevnou velikostí považovat za obsahující prvky. Proto se typ prvku termínu definovaný pro pole používá také s vyrovnávací pamětí s pevnou velikostí. koncová poznámka
Vyrovnávací paměti s pevnou velikostí jsou povoleny pouze v deklarcích struktury a mohou nastat pouze v nebezpečných kontextech (§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 ']'
;
Deklarace vyrovnávací paměti s pevnou velikostí může obsahovat sadu atributů (§22), new
modifikátor (§15.3.5), modifikátory přístupnosti odpovídající kterékoli deklarované přístupové požadavkům povolené pro členy struktury (§16.4.3) a unsafe
modifikátor (§23.2). Atributy a modifikátory se vztahují na všechny členy deklarované deklarací vyrovnávací paměti s pevnou velikostí. Jedná se o chybu, která se u stejného modifikátoru zobrazí vícekrát v deklaraci vyrovnávací paměti s pevnou velikostí.
Deklarace vyrovnávací paměti s pevnou velikostí nesmí obsahovat static
modifikátor.
Typ prvku vyrovnávací paměti deklarace vyrovnávací paměti s pevnou velikostí určuje typ prvku vyrovnávací paměti zavedené deklarací. Typ prvku vyrovnávací paměti je jedním z předdefinovaných typů sbyte
, byte
, short
, ushort
, , int
uint
, long
, ulong
char
, float
, double
nebo bool
.
Za typem prvku vyrovnávací paměti následuje seznam deklarátorů vyrovnávací paměti s pevnou velikostí, z nichž každý zavádí nový člen. Deklarátor vyrovnávací paměti s pevnou velikostí se skládá z identifikátoru, který označuje člena a za ním konstantní výraz uzavřený do [
tokenů a ]
tokenů. Konstantní výraz označuje počet prvků v členu zavedeného deklarátorem vyrovnávací paměti s pevnou velikostí. Typ konstantního výrazu musí být implicitně konvertibilní na typ int
a hodnota musí být nenulové kladné celé číslo.
Prvky vyrovnávací paměti s pevnou velikostí musí být rozloženy postupně v paměti.
Deklarace vyrovnávací paměti s pevnou velikostí, která deklaruje více vyrovnávacích pamětí s pevnou velikostí, je ekvivalentní více deklarací jedné deklarace vyrovnávací paměti s pevnou velikostí se stejnými atributy a typy prvků.
Příklad:
unsafe struct A { public fixed int x[5], y[10], z[100]; }
je ekvivalentem
unsafe struct A { public fixed int x[5]; public fixed int y[10]; public fixed int z[100]; }
end example
23.8.3 Vyrovnávací paměti s pevnou velikostí ve výrazech
Vyhledávání členů (§12.5) člena vyrovnávací paměti s pevnou velikostí postupuje přesně stejně jako vyhledávání člena pole.
Vyrovnávací paměť s pevnou velikostí lze odkazovat ve výrazu pomocí simple_name (§12.8.4), member_access (§12.8.7) nebo element_access (§12.8.12).
Pokud je člen vyrovnávací paměti s pevnou velikostí odkazován jako jednoduchý název, efekt je stejný jako přístup člena formuláře this.I
, kde I
je člen vyrovnávací paměti s pevnou velikostí.
V přístupu člena formuláře E.I
, kde E.
může být implicitní this.
, pokud E
je typu struktury a členské vyhledávání I
v daném typu struktury identifikuje člen s pevnou velikostí, pak E.I
se vyhodnotí a klasifikuje takto:
- Pokud k výrazu
E.I
nedojde v nebezpečném kontextu, dojde k chybě v době kompilace. - Pokud
E
je klasifikovaná jako hodnota, dojde k chybě v době kompilace. - V opačném případě je-li
E
pohyblivá proměnná (§23.4), pak:- Pokud je výraz
E.I
fixed_pointer_initializerE
- Jinak je-li výraz
E.I
primary_no_array_creation_expression (§12.8.12.1) v rámci element_access (§12.8.12) formulářeE.I[J]
, pak je výsledekE.I
ukazatele,P
na první prvek vyrovnávací pamětiI
pevné velikosti vE
a ohraničující element_access je následně vyhodnocen jako pointer_element_access (§23.6.4).P[J]
- V opačném případě dojde k chybě v době kompilace.
- Pokud je výraz
-
E
V opačném případě odkazuje na pevnou proměnnou a výsledek výrazu je ukazatel na první prvek členuI
vyrovnávací paměti s pevnou velikostí vE
. Výsledek je typuS*
, kde S je typI
prvku a je klasifikován jako hodnota.
K následným prvkům vyrovnávací paměti s pevnou velikostí lze přistupovat pomocí operací ukazatele z prvního prvku. Na rozdíl od přístupu k polím je přístup k prvkům vyrovnávací paměti s pevnou velikostí nebezpečnou operací a není kontrolován.
Příklad: Následující deklaruje a používá strukturu se členem vyrovnávací paměti s pevnou velikostí.
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); } }
end example
23.8.4 Kontrola určitého přiřazení
Vyrovnávací paměti pevné velikosti podléhají určité kontrole přiřazení (§9.4) a členy vyrovnávací paměti s pevnou velikostí se ignorují pro účely kontroly určitého přiřazení proměnných typu struktury.
Pokud vnější obsahující proměnnou struktury člen vyrovnávací paměti s pevnou velikostí je statická proměnná, instance proměnné instance nebo prvku pole, prvky vyrovnávací paměti pevné velikosti se automaticky inicializují na jejich výchozí hodnoty (§9.3). Ve všech ostatních případech není definován počáteční obsah vyrovnávací paměti s pevnou velikostí.
23.9 Přidělení zásobníku
Obecné informace o provozovateli naleznete v §12.8.22 .stackalloc
Zde je popsána schopnost tohoto operátoru vést k ukazateli.
Pokud k výrazu stackalloc_expression dojde jako k inicializačnímu výrazu local_variable_declaration (§13.6.2), kde local_variable_type je buď typ ukazatele (§23.3) nebo odvozený (var
), pak výsledek stackalloc_expression je ukazatel na typ T*
, kde T
představuje unmanaged_type ve výrazu stackalloc_expression. V tomto případě je výsledkem ukazatel na začátek přiděleného bloku.
Příklad:
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 }; }
end example
Na rozdíl od přístupu k polím nebo stackalloc
"blokům typu Span<T>
" je přístup k prvkům stackalloc
typu "blok ukazatele" nebezpečnou operací a není kontrolován.
Příklad: V následujícím kódu
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)); } }
stackalloc
Výraz se používá vIntToString
metodě k přidělení vyrovnávací paměti 16 znaků v zásobníku. Vyrovnávací paměť se automaticky zahodí při vrácení metody.Mějte však na paměti, že
IntToString
lze přepsat v nouzovém režimu; to znamená, že bez použití ukazatelů: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(); } }
end example
Konec podmíněného normativního textu
ECMA C# draft specification