Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
24.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 datovými ukazateli a integrálními typy, přijímat adresy proměnných a metod 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
24.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
unsafemodifiká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
unsafemodifiká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; }
unsafemodifikátor zadaný v deklaraci struktury způsobí, že se celý textový rozsah deklarace struktury stane nebezpečným kontextem. Je tedy možné deklarovatLeftpole typu ukazatele aRightpole. 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.konec příkladu
Kromě vytvoření nebezpečného kontextu, který umožňuje použití ukazatelových typů, nemá modifikátor žádný vliv na typ nebo člena.
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
FjednodušeAzpůsobí, že textový rozsahFse stane nebezpečným kontextem, ve kterém lze použít nebezpečné funkce jazyka. V přepsáníFveBnení nutné znovu specifikovat modifikátorunsafe– ledaže by sama metodaFveBpotřebovala 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) {...} }
FProtož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ímunsafemodifikátoru v deklaraci metody, jak je tomu vBpřípadě .konec příkladu
Použití modifikátoru unsafe u částečné deklarace typu (§15.2.7) znamená, že pouze tato konkrétní část je považována za nebezpečný kontext.
24.3 Typy ukazatele
24.3.1 Obecné
Ukazatel je proměnná, která může obsahovat adresu proměnné nebo statické metody označované jako cíl tohoto ukazatele. Ukazatel s hodnotou null je ukazatel null a v současné době neodkazuje na proměnnou nebo statickou metodu. Pokus o přístup k cíli ukazatele se nazývá dereferencing (§24.6.2 a §24.6.4).
V nebezpečném kontextu může být typ (§8.1) pointer_type. Pointer_type může být rovněž druhem matice (§17). Typ_ukazatel lze použít také ve výrazu typu (§12.8.18) mimo nebezpečné prostředí, protože toto použití není nebezpečné.
pointer_type
: dataptr_type
| funcptr_type
| voidptr_type
;
Typ cíle typu 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 (§24.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 – uvolňování paměti nemá žádné znalosti ukazatelů a dat nebo statických metod, 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 odkazů (objektů) mohou obsahovat ukazatele, ale odkazy ukazatelů nesmí obsahovat odkazy.
Pro danou implementaci musí mít všechny typy ukazatelů stejnou velikost a reprezentaci. Hodnota ukazatele null musí být reprezentována všemi bity-nula.
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 není podporováno zabalení a rozbalení pro ukazatele (§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 §24.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 Evymazá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.4).
Výchozí hodnota (§9.3) pro jakýkoli typ ukazatele je null.
Metoda může vrátit hodnotu určitého typu a tento typ může být ukazatel.
Příklad: Pokud je zadán ukazatel na souvislou sekvenci
inta počet prvků této sekvence, stejně jako nějakou dalšíinthodnotu, následující metoda vrátí 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; }konec příkladu
24.3.2 Datové ukazatele
Ukazatel dat je ukazatel schopný obsahovat adresu proměnné s value_type (§8.3.1), funcptr_type (§24.3.3) nebo voidptr_type (§24.3.4).
dataptr_type
: value_type ('*')+
| funcptr_type ('*')+
| voidptr_type ('*')+
;
Dataptr_type je zapsána jako value_type, která je unmanaged_type (§8.8), funcptr_type nebo voidptr_type, následovaná jedním nebo více * tokeny.
Příklad: Některé příklady datových ukazatelů jsou uvedeny v následující tabulce:
Příklad Popis byte*Ukazatel na byteint*[]Jednorozměrné pole ukazatelů na intchar**Ukazatel na ukazatel na chardelegate*<void>*Ukazatel na ukazatel na statickou metodu bez parametrů a návratového voidtypuvoid**Ukazatel na ukazatel na neznámý typ konec příkladu
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
Nenulová hodnota datového ukazatele, který má typ T* , představuje adresu proměnné typu T. Pro přístup k této proměnné lze použít operátor * nepřímých ukazatelů (§24.6.2).
Příklad: Při zadání proměnné
Ptypuint*výraz*Poznačuje proměnnou nalezenouintna adrese obsažené vP. konec příkladu
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 při vrácení volané metody nebo pevný objekt, na který se použil k bodu, již není opraven. Příklad:
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}"); } } }koncová poznámka
V nebezpečném kontextu je k dispozici několik konstruktorů pro práci s datovými ukazateli:
- Unární
*operátor lze použít k provedení nepřímých ukazatelů (§24.6.2). - Operátor
->lze použít pro přístup ke členu struktury ukazatelem (§24.6.3). - Operátor
[]lze použít k indexování ukazatele (§24.6.4). - Unární
&operátor lze použít k získání adresy proměnné (§24.6.5). - Operátory
++mohou být použity k inkrementování a dekrementování ukazatelů (-- -
+Binární a-operátory lze použít k provádění aritmetik ukazatele (§24.6.7). - Operátory
==, ,!=,<><=a>=mohou být použity k porovnání ukazatelů (§24.6.8). - Operátor
stackalloclze použít k přidělení paměti ze zásobníku volání (§24.9). - Příkaz
fixedlze použít k dočasné opravě proměnné, aby bylo možné získat její adresu (§24.7).
24.3.3 Ukazatele funkce
Ukazatel funkce je ukazatel schopný obsahovat adresu statické metody.
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
;
Stejně jako metoda má podpis (§7.6), typ ukazatele funkce má podpis pro typ metody, na který může odkazovat. Tento podpis zahrnuje konvenci volání.
Nenulová hodnota ukazatele funkce s typem T představuje adresu metody, která má podpis kompatibilní s typem T.
Pokud není k dispozici žádná calling_convention_specifier , výchozí hodnota je managed, což vede k použití výchozího mechanismu spouštěcího prostředí. Pomocí unmanaged_calling_convention je možné zadat konkrétní nespravované konvence, jejichž tokeny jsou mapovány na názvy definované implementací s sémantikou definovanou implementací. Sada platných kombinací těchto tokenů je definována implementací.
Poznámka: Calling_convention_specifier umožňuje zvolit potenciálně efektivnější volací mechanismus nebo pro metody napsané v jiných jazycích než C#. ukončit poznámku.
Příklad: Některé příklady typů ukazatelů funkce jsou uvedeny v následující tabulce:
Příklad Popis delegate*<void>Ukazatel na spravovanou metodu, která nemá žádné parametry a návratový voidtypdelegate*<void>[]Pole ukazatelů na spravovanou metodu bez parametrů a návratového voidtypudelegate*<string, string, bool>Ukazatel na spravovanou metodu se dvěma stringparametry a návratovým typembooldelegate*<ref readonly int>Ukazatel na spravovanou metodu, která nemá žádné parametry a vrací ref readonly intdelegate*<delegate*<int>, void>Ukazatel na spravovanou metodu s jedním parametrem, který je ukazatelem na metodu bez parametrů a návratového inttypu, a návratovým typemvoiddelegate* unmanaged[Stdcall]<void>Ukazatel na nespravovanou metodu bez parametrů a návratového voidtypu pomocíStdcallkonvence voláníVezměte v úvahu následující skutečnosti:
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 } } } }Vzhledem k tomu, že funkce ukazatele v maticovém bodu na metody bez parametrů přebírá
&LogadresuLogmetody, která nemá žádné parametry. konec příkladu
unmanaged_calling_convention podporuje malý počet předdefinovaných konvencí (Cdecl, Stdcall, Thiscalla Fastcall, všech kontextových klíčových slov), které mohou být použity samostatně nebo jako identifikátor v seznamu identifikátorů unmanaged_calling_convention . Jiné konvence definované implementací jsou povolené a více konvencí lze kombinovat pomocí seznamu identifikátorů , který může obsahovat jednu nebo více těchto předdefinovaných konvencí. Vyhledávání a zpracování identifikátorů v tomto seznamu se provádí způsobem definovaným implementací.
Příklad: Při implementaci definované konvence
SuppressGCTransitionvolání ,unsafe class C { delegate* unmanaged[SuppressGCTransition]<int, int> fpx; delegate* unmanaged[Stdcall, SuppressGCTransition]<int, int> fpy; }oba případy používají gramatické pravidlo seznamu identifikátorů. konec příkladu
Vlastní atributy nelze použít na funcptr_type ani na žádný z jeho prvků.
Parametr typu funcptr_type nesmí být označen jako params(§15.6.2.1).
V nebezpečném kontextu jsou k dispozici následující konstrukce pro práci s ukazateli funkce:
- Operátor
&lze použít k získání adresy statické metody (§24.6.5). - Operátory
==, ,!=,<><=a=>mohou být použity k porovnání ukazatelů (§24.6.8). - Operátor invocation_expression
()lze použít k volání metody, na kterou odkazuje (§12.8.9.1).
24.3.4 Void ukazatele
Ukazatel void je ukazatel schopný obsahovat hodnotu datového ukazatele nebo ukazatele funkce.
voidptr_type
: 'void' '*'
;
Voidptr_type se zapíše jako klíčové slovo void následované tokenem tvého*.
Příklad: Některé příklady typů ukazatelů void jsou uvedeny v následující tabulce:
Příklad Popis void*Ukazatel na neznámý typ void*[,,]Trojrozměrné pole ukazatelů na neznámý typ konec příkladu
Voidptr_type 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 typ ukazatele (a naopak) a porovnat s hodnotami jiných typů ukazatele (§24.6.8).
24.4 Pevné a pohyblivé proměnné
Operátor adresy (§24.6.5) a fixed výrok (§24.7) rozdělují proměnné do dvou kategorií: Pevné proměnnés 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é zpětným odvozováním datových ukazatelů.) 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 & (§24.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 statement24.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 proměnná není zachycena neanonymní
staticfunkcí (§12.22.6.2). - Proměnná vznikající z member_access (§12.8.7) ve formě
V.I, kdeVje pevná proměnná typu struct_type. - Proměnná vyplývající z pointer_indirection_expression (§24.6.2) formuláře
*P, pointer_member_access (§24.6.3) formulářeP->Inebo pointer_element_access (§24.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 datového ukazatele vždy klasifikována jako pevná proměnná.
24.5 Převody ukazatelů
24.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*. - Od null_literal (§6.4.5.7) do libovolného pointer_type.
- Od funcptr_type
F0po funcptr_typeF1platí všechny následující skutečnosti:-
F0aF1mají stejný počet parametrů a každý parametrD0nmáF0stejné modifikátory podle odkazu jako odpovídající parametrD1nvF1. - Pro každý parametr hodnoty existuje převod identity, implicitní převod odkazu nebo implicitní převod ukazatele z typu
F0parametru do odpovídajícího typu parametru vF1. - Pro každý parametr podle odkazu je typ
F0parametru stejný jako odpovídající typ parametru vF1. - Pokud návratový typ je podle hodnoty, identita, implicitní odkaz nebo implicitní převod ukazatele existuje z návratového
F1typu na návratovýF0typ . - Pokud je návratový typ odkazem, návratový typ a
refmodifikátoryF1jsou stejné jako návratový typ arefmodifikátoryF0. - Konvence volání
F0je stejná jako konvence voláníF1.
-
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, ,nintnuintuintlongintneboulongdo jakéhokoli pointer_type. - Z jakéhokoli pointer_type do
sbyte,byte,uintushortnintshortint,nuint, ,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*. - Od null_literal 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 dataptr_type, pokud výsledný ukazatel není správně zarovnaný pro typ point-to-type, 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 }konec příkladu
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(); } } }Produkovaný výstup samozřejmě závisí na endianitě. Jednou z možností je
" BA FF 51 A2 90 6C 24 45".konec příkladu
Mapování mezi ukazateli a celými čísly jsou definována implementací.
Poznámka: Na 32bitových a 64bitových procesorových architekturách s lineárním adresním prostorem se převody ukazatelů na nebo z integrálních typů obvykle chovají přesně stejně jako převody
uintneboulonghodnot na nebo z těchto integrálních typů. koncová poznámka
24.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.Arraya rozhraní, která implementuje, platí také pro pole ukazatelů. Jakýkoli pokus o přístup k prvkům pole prostřednictvímSystem.Arraynebo rozhraní, která implementuje, může vést k výjimce za běhu, protože typy ukazatelů nejsou konvertibilní naobject. - Implicitní a explicitní převody odkazů (§10.2.8, §10.3.5) z jednorozměrného typu pole
S[]naSystem.Collections.Generic.IList<T>a jeho obecná základní rozhraní se nikdy nevztahují na pole ukazatelů. - Explicitní převod odkazu (§10.3.5) z
System.Arraya rozhraní, kteráSystem.Arrayimplementuje, na array_type, se vztahuje na ukazatelové pole. - Explicitní převody odkazů (§10.3.5) z
System.Collections.Generic.IList<S>a jeho základních rozhraní na jednorozměrný typ poleT[]se nikdy nevztahují na pole ukazatelů, protože typy ukazatelů nelze použít jako argumenty typu a neexistují žádné převody z typů ukazatelů na typy bez ukazatelů.
Tato omezení znamenají, že rozšíření foreach příkazu pro pole popsaná 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 nebo embedded_statement ani žádnému jinému zdrojovému kódu programu. Proměnná v je v vloženém příkazu jen pro čtení. Pokud neexistuje explicitní převod (§24.5) z T (typu prvku) do V, dojde k 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
24.6 Ukazatele ve výrazech
24.6.1 Obecné
V nebezpečném kontextu může výraz vrátit výsledek typu ukazatele, ale mimo nebezpečný kontext je chybou při kompilaci, pokud je 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 umožňují produkce primary_expression (§12.8) a unary_expression (§12.9) další konstrukce, které jsou popsány v následujících dílčích klauzulích.
Poznámka: Priorita a asociativita nebezpečných operátorů je odvozena gramatikou. koncová poznámka
Všechny aspekty odvozování typu s ohledem na ukazatele funkce jsou popsány v odpovídajících dílčích nástavcích §12.6 a §12.8.
24.6.2 Nepřímý ukazatel
Výraz pro nepřímé odkazování ukazatele se skládá z hvězdičky (*) následované unárním výrazem.
pointer_indirection_expression
: '*' unary_expression
;
Unární * operátor označuje nepřímý ukazatel a slouží k získání proměnné, na kterou datový 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 operand s typem funcptr_type nebo voidptr_type.
Poznámka: V jazyce C/C++ lze ukazatel funkce dereferenced získat u základní funkce, aby ji volal, jako v
(*fp)(). Takové explicitní dereferencování není v jazyce C# povoleno. koncová poznámka
Účinek použití unárního * operátoru na ukazatel dat null je definován implementací. Konkrétně neexistuje žádná záruka, že tato operace vyvolá System.NullReferenceException.
Pokud je k datovému ukazateli přiřazena neplatná hodnota, není definováno chování unárního * operátoru.
Poznámka: Mezi neplatné hodnoty pro dereferencování ukazatele dat unárním
*operátorem jsou adresa nesprávně zarovnaná pro typ, na který odkazuje (viz příklad v §24.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).
24.6.3 Přístup ke členu ukazatele
Pointer_member_access se skládá z primary_expression, za nímž následuje 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->IP musí být výraz datového typu ukazatele a I označuje přístupný člen typu, na který P odkazuje. Jedná se o chybu v době kompilace, P kdy má typ funcptr_type nebo voidptr_type.
Přístup k členu ukazatele ve tvaru P->I je vyhodnocen stejně jako (*P).I. Popis operátoru nepřímého ukazatele (*), viz §24.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->Ije přesně ekvivalentní(*P).I,Mainmetoda 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()); } } }konec příkladu
Přístup k prvku ukazatele 24.6.4
Pointer_element_access se skládá z primary_expression následovaného výrazem uzavřeným v „[“ a „]“.
pointer_element_access
: primary_expression '[' expression ']'
;
Při uznání primary_expression , pokud jsou alternativy element_access i pointer_element_access (§24.6.4) použitelné, je-li vložený primary_expression typu ukazatele (§24.3).
Při 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, , nint, nuint, , longnebo ulong.
Přístup prvku ukazatele formuláře P[E] je vyhodnocen přesně jako *(P + E). Popis operátoru nepřímého ukazatele (*), viz §24.6.2. Popis operátoru sčítání ukazatele (+), viz §24.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 pomocí ukazatele je použit k inicializaci mezipamě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; } } } }konec příkladu
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
24.6.5 Operátor adresy
Addressof_expression se skládá z ampersandu (&) následovaného unary_expression.
addressof_expression
: '&' unary_expression
;
unary_expression určí proměnnou nebo skupinu metod. Případ proměnné je popsán bezprostředně níže.
Vzhledem k výrazu E , který je typu T a je klasifikován jako pevná proměnná (§24.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 (§24.7).
Poznámka: Jak je uvedeno v §12.8.7, mimo konstruktor instance nebo statický konstruktor pro strukturu nebo třídu, která definuje
readonlypole, je toto pole považováno za hodnotu, nikoli proměnnou. Proto jeho adresu nelze získat. Podobně nelze vzít adresu konstanty. koncová poznámka
Operátor & nevyžaduje, aby byl jeho operand rozhodně přiřazen, ale po & operaci je proměnná, ke které se operátor použije, považována 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); } }
ise považuje za rozhodně přiřazené po operaci&i, která byla použita k inicializacip. Přiřazení, které*pmá 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.konec příkladu
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. Například mnoho externích rozhraní API používá ukazatel ke struktuře, kterou API vyplní. 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 (§24.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
Případ unary_expression určení skupiny metod je popsán bezprostředně níže.
V nebezpečném kontextu je metoda M kompatibilní s funcptr_typeF , pokud jsou splněny všechny následující podmínky:
-
MaFmají stejný počet parametrů a každý parametr vMmá stejnýref,outnebo modifikátoryinjako odpovídající parametr vF. - Pro každý parametr hodnoty existuje převod identity, implicitní převod odkazu nebo implicitní převod ukazatele z typu
Mparametru do odpovídajícího typu parametru vF. - Pro každý parametr podle odkazu je typ
Mparametru stejný jako odpovídající typ parametru vF. - Pokud návratový typ je podle hodnoty, identita, implicitní odkaz nebo implicitní převod ukazatele existuje z návratového
Ftypu na návratovýMtyp . - Pokud je návratový typ odkazem, návratový typ a
refmodifikátoryFjsou stejné jako návratový typ arefmodifikátoryM. - Konvence volání
Mje stejná jako konvence voláníF. -
Mje statická metoda.
Implicitní převod existuje z unary_expression , jehož cílem je skupina Emetod , na kompatibilní typ F ukazatele funkce, pokud E obsahuje alespoň jednu metodu, která je použitelná v normální podobě na seznam argumentů vytvořený pomocí typů parametrů a modifikátorů F, jak je popsáno v následujících:
- Jedna metoda
Mje vybrána odpovídající metodě vyvolání formulářeE(A)s následujícími úpravami:- Seznam argumentů
Aje seznam výrazů, každý klasifikovaný jako proměnná a s typem a modifikátorem odpovídajícího funcptr_parameter_list .F - Kandidátské metody jsou pouze metody, které jsou použitelné ve své normální podobě, nikoli v rozšířené podobě.
- Kandidátské metody jsou pouze metody, které jsou statické.
- Seznam argumentů
- Pokud algoritmus řešení přetížení vytvoří chybu, dojde k chybě v době kompilace. Jinak algoritmus vytvoří jednu nejlepší metodu
Mse stejným počtem parametrů jakoFa převod se považuje za existující. - Vybraná metoda
Mmusí být kompatibilní (jak je definováno výše) s typemFukazatele funkce . V opačném případě dojde k chybě kompilace. - Výsledkem převodu je ukazatel funkce typu
F.
24.6.6 Ukazatel se zvýší a sníží
V nezabezpečeném kontextu ++ lze operátory a -- operátory (§12.8.16 a §12.9.7) použít u proměnných ukazatelů všech typů. Jedná se o chybu v době kompilace, aby se tyto operátory použily na proměnné typu funcptr_type nebo voidptr_type. Proto pro každý datový 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-1v uvedeném pořadí (§24.6.7). Jinými slovy, pro proměnnou ukazatele dat typu T*++ se operátor přidá sizeof(T) k adrese obsažené v proměnné a -- operátor odečte sizeof(T) od adresy obsažené v proměnné.
Pokud operace zvýšení nebo dekrementace ukazatele přeteče domény typu ukazatele, výsledek je definovaný implementací a nevyžaduje se žádná výjimka.
24.6.7 Aritmetika ukazatele
V nezabezpečeném kontextu + lze operátor (§12.13.5) a - operátor (§12.13.6) použít na hodnoty všech datových typů ukazatele. Jedná se o chybu v době kompilace, kdy se tyto operátory použijí na hodnotu typu funcptr_type nebo voidptr_type. 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);
Neexistují žádné předdefinované operátory pro sčítání nebo odčítání ukazatele s nativním celočíselnou (§8.3.6) posuny.
nint
nuint Místo toho se hodnoty upřednostní na long a ulong, v uvedeném pořadí s aritmetikou ukazatele pomocí předdefinovaných operátorů pro tyto typy.
Vzhledem k výrazu datového typu T* ukazatele a výrazu N typu int, uintlong, nebo ulong, výrazy P + N a N + P vypočítá hodnotu ukazatele typuT*, která je výsledkem přidání N * sizeof(T) na adresu danou P.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, datového ukazatele typu T*, 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 = 14konec příkladu
Pokud aritmetická operace ukazatele přeteče doménu typu ukazatele, výsledek je zkrácen způsobem definovaným implementací a nevyžaduje se žádná výjimka.
24.6.8 Porovnání ukazatelů
V nezabezpečeném kontextu ==lze bezpečně použít operátory , , <=!=><, a >= operátory (§12.15) na hodnoty všech dataptr_types a na hodnoty všech voidptr_types, které jsou kopiemi dataptr_type hodnot. 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. Chování při porovnávání hodnot funcptr_typenebo void* jejich kopií však není definováno.
Poznámka: Na některých platformách je možné, že když je adresa dané metody přijata vícekrát, výsledky se liší a porovnávání s nimi nespolehlivý. koncová poznámka
24.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, uvnitř i na konci struktury existovat nepojmenovaná výplň. Obsah bitů použitých jako výplň 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í.
24.7 Pevný příkaz
V nezabezpečeném kontextu produkce embedded_statement (§13.1) umožňuje použití další konstrukce, pevného výrazu, který se používá k zajištění pohyblivé proměnné tak, aby její adresa zůstala konstantní po dobu trvání výrazu.
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 nastaví tuto místní proměnnou na adresu vypočítanou odpovídajícím fixed_pointer_initializer.
pointer_type nesmí být funcptr_type. Místní proměnná deklarovaná v pevné instrukci je přístupná v libovolných fixed_pointer_initializerech, které se vyskytují napravo od deklarace této proměnné, a v embedded_statement pevné instrukce. 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.22.6.2), parametru hodnoty nebo pole parametrů v fixed_pointer_initializer. Fixed_pointer_initializer může mít jednu z následujících možností:
- Token "
&" následovaný variable_reference (§9.5) do pohyblivé proměnné (§24.4) nespravovaného typuTza předpokladu, že typT*je implicitně konvertibilní na typ ukazatele zadaný vfixedpří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ífixedpříkazu. Pokud jenullvýraz pole nebo pokud má pole nula prvků, inicializátor vypočítá adresu rovné nule. - Výraz typu
string, za předpokladu, že typchar*je implicitně konvertibilní na typ ukazatele zadaný vfixedpří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ífixedpříkazu. Chování příkazufixedzávisí na implementaci, pokud jenullřetězcový výraz. - Výraz jiného typu než array_type nebo
stringza předpokladu, že existuje přístupná metoda nebo přístupná rozšiřující metoda odpovídající podpisuref [readonly] T GetPinnableReference(), kdeTje unmanaged_type aT*implicitně se konvertibilní na typ ukazatele zadaný vfixedpří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ífixedpříkazu. MetoduGetPinnableReference()může použít příkazfixed, když rozlišení přetížení (§12.6.4) určí přesně jednoho člena funkce a tento člen splňuje předchozí podmínky. MetodaGetPinnableReferenceby měla vrátit odkaz na adresu rovnající se nule, jako ta vrácená zSystem.Runtime.CompilerServices.Unsafe.NullRef<T>()když nejsou žádná data k připnutí. - "Jednoduchý_název nebo member_access, který odkazuje na člena vyrovnávací paměti s pevnou velikostí pohyblivé proměnné, pokud je typ člena vyrovnávací paměti s pevnou velikostí implicitně převeditelný na typ ukazatele zadaný v příkazu
fixed." V tomto případě inicializátor vypočítá ukazatel na první prvek vyrovnávací paměti s pevnou velikostí (§24.8.3) a vyrovnávací paměť s pevnou velikostí je zaručena zůstat na pevné adrese po dobu trvánífixedpříkazu.
Pro každou adresu vypočítanou pomocí fixed_pointer_initializer příkaz zajistí, že proměnná, na kterou adresa odkazuje, nebude po dobu trvání fixed příkazu předmětem přemístění nebo odstranění uvolňováním paměti.
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. konec příkladu
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
fixedpředány externím rozhraním API, je zodpovědností programátora zajistit, aby rozhraní API nezachovávají žádnou paměť těchto ukazatelů. konec příkladu
Pevné objekty můžou způsobit fragmentaci haldy (protože je nelze 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í
fixedpříkazu. První příkaz zafixuje a získá adresu statického pole, druhý příkaz zafixuje a získá adresu instančního pole a třetí příkaz zafixuje a získá adresu prvku 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é
fixedpříkazy v příkladu výše vytvářejí identické výsledky. Obecně platí, že pro instanciapole , určenía[0]vfixedpříkazu je stejné jako jednoduše zadata.konec příkladu
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 p na instanci pole a, hodnoty ukazatele v rozmezí od p do p + a.Length - 1 představují adresy prvků v poli. Podobně proměnné v rozsahu od p[0] do p[a.Length - 1] představují 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] = 23konec příkladu
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
fixedslouží k opravě pole, aby jeho adresa byla předána metodě, která přebírá ukazatel.konec příkladu
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 p na instanci řetězce, hodnoty ukazatele s, které se pohybují v rozmezí od p do p + s.Length ‑ 1, představují adresy znaků v řetězci a hodnota ukazatele p + s.Length vždy odkazuje na nulový znak (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); } } }konec příkladu
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
Cmá přístupnouGetPinnableReferencemetodu se správným podpisem. V příkazufixedseref int, vrácený z této metody, použije k inicializaci ukazatelecint*při volání nap. konec příkladu
Ú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 nulovým znakem zobrazí zkrácený
char*. koncová poznámka
24.8 Vyrovnávací paměti s pevnou velikostí
24.8.1 Obecné
Vyrovnávací paměti s pevnou velikostí se používají k deklaraci polí ve stylu jazyka C jako členů struktur a jsou především užitečné pro komunikaci s nespravovanými rozhraními API.
24.8.2 Deklarace vyrovnávací paměti s pevnou velikostí
Pevně velký buffer je člen, který představuje úložiště pro fixně dlouhý buffer 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: Podobně jako pole je možné si představit, že vyrovnávací paměť s pevnou velikostí obsahuje prvky. Proto se termín typ prvku který je definován pro pole používá také s pevně velikostním vyrovnávacím bufferem. 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 (§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 ']'
;
Deklarace vyrovnávací paměti s pevnou velikostí může obsahovat sadu atributů (§23), new modifikátor (§15.3.5), modifikátory přístupnosti odpovídající kterékoli deklarované přístupové pravděpodobnosti povolené pro členy struktury (§16.4.3) a unsafe modifikátor (§24.2). Atributy a modifikátory se vztahují na všechny členy deklarované deklarací vyrovnávací paměti s pevnou velikostí. Opakované použití stejného modifikátoru v deklaraci vyrovnávací paměti s pevnou velikostí je chybné.
Deklarace vyrovnávací paměti s pevnou velikostí nesmí obsahovat static modifikátor.
Typ prvku deklarace vyrovnávací paměti s pevnou velikostí určuje typ prvku vyrovnávacích pamětí definovaných touto deklarací. Typ prvku vyrovnávací paměti je jeden z předdefinovaných typů sbyte, byte, short, ushort, , uintint, nint, nuintcharulongfloatlongnebo . doublebool
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ý pojmenovává člena, a za ním následuje konstantní výraz uzavřený v tokeny [ a ]. 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 inta hodnota musí být nenulové kladné celé číslo.
Prvky vyrovnávací paměti s pevnou velikostí by měly 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]; }konec příkladu
24.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 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í.
Při přístupu člena formy E.I, kde E. může být implicitní this., pokud E je strukturového typu a členské vyhledávání I v určitém strukturovém typu identifikuje člena s pevnou velikostí, pak E.I se vyhodnotí a klasifikuje takto:
- Pokud k výrazu
E.Inedojde v nebezpečném kontextu, dojde k chybě v době kompilace. - Pokud
Eje klasifikovaná jako hodnota, dojde k chybě v době kompilace. - Jinak je-li
Epohyblivá proměnná (§24.4), pak:- Pokud je výraz
E.Ifixed_pointer_initializer (§24.7), je výsledkem výrazu ukazatel na první prvek členuIvyrovnávací paměti pevné velikosti vE. - Jinak je-li výraz
E.Iprimary_expression (§12.8.12.1) v rámci element_access (§12.8.12) formulářeE.I[J], pak je výsledkemE.IukazatelP, na první prvek vyrovnávací pamětiIpevné velikosti vEa ohraničující element_access se pak vyhodnotí jako pointer_element_access (§24.6.4).P[J] - V opačném případě dojde k chybě v době kompilace.
- Pokud je výraz
-
EV opačném případě odkazuje na pevnou proměnnou a výsledek výrazu je ukazatel na první prvek členuIvyrovnávací paměti s pevnou velikostí vE. Výsledek je typuS*, kde S je typIprvku 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); } }konec příkladu
24.8.4 Kontrola určitého přiřazení
Vyrovnávací paměti s pevnou velikostí nepodléhají kontrole definitivního přiřazení (§9.4) a členy vyrovnávací paměti pevné velikosti se ignorují pro účely této kontroly u proměnných typu struktury.
Pokud je vnější struktura obsahující proměnnou členem vyrovnávací paměti s pevnou velikostí statickou proměnnou, instanční proměnnou třídy nebo prvkem pole, prvky vyrovnávací paměti pevné velikosti se automaticky inicializují na své 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í.
24.9 Přidělení zásobníku
Obecné informace o provozovateli naleznete v §12.8.22 .stackalloc Zde se diskutuje schopnost tohoto operátoru mít za výsledek ukazatel.
Pokud stackalloc_expression nastane jako inicializační výraz local_variable_declaration (§13.6.2), kde local_variable_type je typ ukazatele (§24.3) nebo odvozený (var), výsledek stackalloc_expression je ukazatel typu T*, kde T je unmanaged_typestackalloc_expression. V tomto případě je výsledkem ukazatel na začátek přiděleného bloku.
Ve všech ostatních ohledech se sémantika local_variable_declarations (§13.6.2) a stackalloc_expressions (§12.8.22) v nebezpečných kontextech řídí těmi, které jsou definovány pro bezpečné kontexty.
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 }; // 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 }; }konec příkladu
Na rozdíl od přístupu k polím nebo stackalloc blokům typu Span<T> je přístup k prvkům stackalloc bloku ukazatelového typu nebezpečnou operací a není kontrolován rozsahem.
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)); } }
stackallocVýraz se používá vIntToStringmetodě 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
IntToStringlze 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(); } }konec příkladu
Konec podmíněného normativního textu
ECMA C# draft specification