23 Osäker kod
23.1 Allmänt
En implementering som inte stöder osäker kod krävs för att diagnostisera all användning av de syntaktiska regler som definieras i den här satsen.
Resten av denna klausul, inklusive alla dess underklisklar, är villkorligt normativ.
Obs! C#-kärnspråket, enligt definitionen i föregående satser, skiljer sig särskilt från C och C++ i utelämnandet av pekare som datatyp. I stället tillhandahåller C# referenser och möjligheten att skapa objekt som hanteras av en skräpinsamlare. Den här designen, tillsammans med andra funktioner, gör C# till ett mycket säkrare språk än C eller C++. I C#-kärnspråket går det helt enkelt inte att ha en ennitialiserad variabel, en "dinglande" pekare eller ett uttryck som indexerar en matris utanför dess gränser. Hela kategorier av buggar som rutinmässigt plågar C- och C++-program elimineras därmed.
Även om praktiskt taget varje pekartypskonstruktion i C eller C++ har en referenstypsmotsvarighet i C#, finns det dock situationer där åtkomst till pekartyper blir en nödvändighet. Till exempel kanske det inte är möjligt eller praktiskt att interagera med det underliggande operativsystemet, komma åt en minnesmappad enhet eller implementera en tidskritisk algoritm utan åtkomst till pekare. För att åtgärda detta behov ger C# möjlighet att skriva osäker kod.
I osäker kod är det möjligt att deklarera och arbeta med pekare, att utföra konverteringar mellan pekare och integraltyper, att ta adressen till variabler och så vidare. På sätt och vis är det ungefär som att skriva osäker kod som att skriva C-kod i ett C#-program.
Osäker kod är i själva verket en "säker" funktion ur både utvecklares och användares perspektiv. Osäker kod ska vara tydligt markerad med modifieraren
unsafe
, så utvecklare kan eventuellt inte använda osäkra funktioner av misstag, och körningsmotorn fungerar för att säkerställa att osäker kod inte kan köras i en obetrodd miljö.slutkommentar
23.2 Osäkra kontexter
De osäkra funktionerna i C# är endast tillgängliga i osäkra kontexter. En osäker kontext introduceras genom att en unsafe
modifierare inkluderas i deklarationen av en typ, medlem eller lokal funktion eller genom att använda en unsafe_statement:
- En deklaration av en klass, struct, gränssnitt eller ombud kan innehålla en
unsafe
modifierare, i vilket fall hela textinnehållet i den typdeklarationen (inklusive brödtexten för klassen, struct eller gränssnittet) anses vara en osäker kontext.Obs! Om type_declaration är partiell är endast den delen en osäker kontext. slutkommentar
- En deklaration av ett fält, en metod, en egenskap, en händelse, indexerare, operator, instanskonstruktor, finalizer, statisk konstruktor eller lokal funktion kan innehålla en
unsafe
modifierare, i vilket fall hela text omfattningen av medlemsdeklarationen anses vara en osäker kontext. - Med en unsafe_statement kan du använda en osäker kontext i ett block. Hela textmängden för det associerade blocket anses vara en osäker kontext. En lokal funktion som deklareras i en osäker kontext är i sig osäker.
De associerade grammatiktilläggen visas nedan och i efterföljande underklisklar.
unsafe_modifier
: 'unsafe'
;
unsafe_statement
: 'unsafe' block
;
Exempel: I följande kod
public unsafe struct Node { public int Value; public Node* Left; public Node* Right; }
Modifieraren
unsafe
som anges i struct-deklarationen gör att hela den textmässiga omfattningen av struct-deklarationen blir en osäker kontext. Därför är det möjligt att deklarera fältenLeft
ochRight
vara av pekartyp. Exemplet ovan kan också skrivaspublic struct Node { public int Value; public unsafe Node* Left; public unsafe Node* Right; }
unsafe
Här gör modifierarna i fältdeklarationerna att dessa deklarationer betraktas som osäkra kontexter.slutexempel
Förutom att upprätta en osäker kontext, vilket tillåter användning av pekartyper, unsafe
har modifieraren ingen effekt på en typ eller medlem.
Exempel: I följande kod
public class A { public unsafe virtual void F() { char* p; ... } } public class B : A { public override void F() { base.F(); ... } }
Den osäkra modifieraren på
F
metoden iA
leder helt enkelt till att textens omfattningF
blir en osäker kontext där språkets osäkra funktioner kan användas. I åsidosättningen avF
iB
behöver du inte angeunsafe
modifieraren igen, såvidaF
inte metoden iB
sig själv behöver åtkomst till osäkra funktioner.Situationen är något annorlunda när en pekartyp är en del av metodens signatur
public unsafe class A { public virtual void F(char* p) {...} } public class B: A { public unsafe override void F(char* p) {...} }
F
Eftersom signaturen innehåller en pekartyp kan den bara skrivas i en osäker kontext. Den osäkra kontexten kan dock introduceras genom att antingen göra hela klassen osäker, vilket är fallet iA
, eller genom att inkludera enunsafe
modifierare i metoddeklarationen, vilket är fallet iB
.slutexempel
När modifieraren används i en partiell typdeklaration (§15.2.7) betraktas endast den unsafe
specifika delen som ett osäkert sammanhang.
23.3 Pekartyper
I ett osäkert sammanhang kan en typ (§8.1) vara en pointer_type samt en value_type, en reference_type eller en type_parameter. I ett osäkert sammanhang kan en pointer_type också vara elementtypen för en matris (§17). En pointer_type kan också användas i ett typuttryck (§12.8.18) utanför ett osäkert sammanhang (eftersom sådan användning inte är osäker).
En pointer_type skrivs som en unmanaged_type (§8.8) eller nyckelordet void
, följt av en *
token:
pointer_type
: value_type ('*')+
| 'void' ('*')+
;
Den typ som angavs före *
i en pekartyp kallas referenstypen för pekartypen. Den representerar typen av variabel som ett värde av pekartypen pekare pekar på.
En pointer_type får endast användas i en array_type i ett osäkert sammanhang (§23.2). En non_array_type är en typ som inte i sig är en array_type.
Till skillnad från referenser (värden för referenstyper) spåras inte pekare av skräpinsamlaren – skräpinsamlaren har ingen kunskap om pekare och de data som de pekar mot. Därför är en pekare inte tillåten att peka på en referens eller till en struct som innehåller referenser, och referenstypen för en pekare ska vara en unmanaged_type. Själva pekartyperna är ohanterade typer, så en pekartyp kan användas som referenstyp för en annan pekartyp.
Den intuitiva regeln för blandning av pekare och referenser är att referenser till referenser (objekt) tillåts innehålla pekare, men referenser till pekare tillåts inte innehålla referenser.
Exempel: Några exempel på pekartyper finns i tabellen nedan:
Exempel Beskrivning byte*
Pekare till byte
char*
Pekare till char
int**
Pekare till pekare till int
int*[]
Endimensionell matris med pekare till int
void*
Pekare till okänd typ slutexempel
För ett visst genomförande ska alla pekartyper ha samma storlek och representation.
Obs! Till skillnad från C och C++, när flera pekare deklareras i samma deklaration, skrivs i C#
*
endast den underliggande typen, inte som prefixpunktion på varje pekarnamn. Till exempel:int* pi, pj; // NOT as int *pi, *pj;
slutkommentar
Värdet för en pekare som har typen T*
representerar adressen för en variabel av typen T
. Pekarens indirekta operator *
(§23.6.2) kan användas för att komma åt den här variabeln.
Exempel: Med en variabel
P
av typenint*
anger uttrycket*P
variabelnint
som finns på adressen iP
. slutexempel
Precis som en objektreferens kan en pekare vara null
. Om du tillämpar indirektionsoperatorn på en null
-värderad pekare resulterar det i implementeringsdefinierat beteende (§23.6.2). En pekare med värde null
representeras av all-bits-zero.
Typen void*
representerar en pekare till en okänd typ. Eftersom referenstypen är okänd kan inte indirekta operatorn tillämpas på en pekare av typen void*
, och inte heller kan någon aritmetik utföras på en sådan pekare. En pekare av typen void*
kan dock gjutas till valfri annan pekartyp (och vice versa) och jämföras med värden för andra pekartyper (§23.6.8).
Pekartyper är en separat kategori av typer. Till skillnad från referenstyper och värdetyper ärver pekartyper inte från object
och det finns inga konverteringar mellan pekartyper och object
. I synnerhet stöds inte boxning och unboxing (§8.3.13) för pekare. Konverteringar tillåts dock mellan olika pekartyper och mellan pekartyper och integraltyper. Detta beskrivs i §23.5.
En pointer_type kan inte användas som ett typargument (§8.4) och typinferens (§12.6.3) misslyckas med generiska metodanrop som skulle ha härledt ett typargument till en pekartyp.
En pointer_type kan inte användas som en typ av underuttryck av en dynamiskt bunden åtgärd (§12.3.3).
En pointer_type kan inte användas som typ av den första parametern i en tilläggsmetod (§15.6.10).
En pointer_type kan användas som typ av ett flyktigt fält (§15.5.4).
Den dynamiska radering av en typ E*
är pekartypen med referenstypen för den dynamiska radering av E
.
Ett uttryck med pekartyp kan inte användas för att ange värdet i en member_declarator inom en anonymous_object_creation_expression (§12.8.17.7).
Standardvärdet (§9.3) för alla pekartyper är null
.
Obs! Även om pekare kan skickas som bireferensparametrar kan detta orsaka odefinierat beteende, eftersom pekaren mycket väl kan vara inställd på att peka på en lokal variabel som inte längre finns när den anropade metoden returnerar, eller det fasta objekt som den använde för att peka, inte längre är fast. Till exempel:
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 } } }
slutkommentar
En metod kan returnera ett värde av någon typ och den typen kan vara en pekare.
Exempel: När du ger en pekare till en sammanhängande sekvens med
int
s, sekvensens elementantal och något annatint
värde returnerar följande metod adressen för det värdet i sekvensen, om en matchning inträffar. Annars returnerasnull
: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; }
slutexempel
I ett osäkert sammanhang är flera konstruktioner tillgängliga för att arbeta med pekare:
- Den unary
*
operatorn kan användas för att utföra pekare indirection (§23.6.2). - Operatören
->
kan användas för att komma åt en medlem i en struct genom en pekare (§23.6.3). - Operatorn
[]
kan användas för att indexera en pekare (§23.6.4). - Den unary
&
operatorn kan användas för att erhålla adressen för en variabel (§23.6.5). - Operatorerna
++
och--
kan användas för att öka och minska pekare (§23.6.6). - Binär-
+
och operatorerna kan användas för att utföra pekarearitmetik (§23.6.7-
). - Operatorerna
==
,!=
,<
,>
,<=
och>=
kan användas för att jämföra pekare (§23.6.8). - Operatorn
stackalloc
kan användas för att allokera minne från anropsstacken (§23.9). - Instruktionen
fixed
kan användas för att tillfälligt åtgärda en variabel så att dess adress kan erhållas (§23.7).
23.4 Fasta och flyttbara variabler
Operatorns adress (§23.6.5) och instruktionen fixed
(§23.7) delar upp variabler i två kategorier: Fasta variabler och flyttbara variabler.
Fasta variabler finns på lagringsplatser som inte påverkas av driften av skräpinsamlaren. (Exempel på fasta variabler är lokala variabler, värdeparametrar och variabler som skapats av avreferencing-pekare.) Å andra sidan finns flyttbara variabler på lagringsplatser som är föremål för flytt eller bortskaffande av skräpinsamlaren. (Exempel på flyttbara variabler är fält i objekt och element i matriser.)
Operatören &
(§23.6.5) tillåter att adressen till en fast variabel erhålls utan begränsningar. Men eftersom en flyttbar variabel kan flyttas eller bortskaffas av skräpinsamlaren kan adressen för en flyttbar variabel endast erhållas med hjälp av en fixed statement
(§23.7), och den adressen är endast giltig under fixed
hela instruktionen.
I exakta termer är en fast variabel något av följande:
- En variabel som härrör från en simple_name (§12.8.4) som refererar till en lokal variabel, värdeparameter eller parametermatris, såvida inte variabeln avbildas av en anonym funktion (§12.19.6.2).
- En variabel som härrör från en member_access (§12.8.7) i formuläret
V.I
, därV
är en fast variabel för en struct_type. - En variabel som resulterar från en pointer_indirection_expression (§23.6.2) av bilda
*P
, en pointer_member_access (§23.6.3) av bildaP->I
, eller en pointer_element_access (§23.6.4) av bildaP[E]
.
Alla andra variabler klassificeras som flyttbara variabler.
Ett statiskt fält klassificeras som en flyttbar variabel. Dessutom klassificeras en bireferensparameter som en flyttbar variabel, även om argumentet för parametern är en fast variabel. Slutligen klassificeras alltid en variabel som skapas genom att en pekare avrefereras som en fast variabel.
23.5 Pekarkonverteringar
23.5.1 Allmänt
I ett osäkert sammanhang utökas uppsättningen med tillgängliga implicita konverteringar (§10.2) till att omfatta följande implicita pekarkonverteringar:
- Från valfri pointer_type till typen
void*
. - Från literalen
null
(§6.4.5.7) till alla pointer_type.
I ett osäkert sammanhang utökas dessutom uppsättningen med tillgängliga explicita konverteringar (§10.3) till att omfatta följande explicita pekarkonverteringar:
- Från alla pointer_type till andra pointer_type.
- Från
sbyte
,byte
,short
,ushort
,int
,uint
,long
ellerulong
till någon pointer_type. - Från alla pointer_type till
sbyte
,byte
,short
,ushort
,int
,uint
,long
, ellerulong
.
I ett osäkert sammanhang innehåller slutligen uppsättningen implicita standardkonverteringar (§10.4.2) följande pekarkonverteringar:
- Från valfri pointer_type till typen
void*
. - Från literalen
null
till alla pointer_type.
Konverteringar mellan två pekartyper ändrar aldrig det faktiska pekarvärdet. Med andra ord har en konvertering från en pekartyp till en annan ingen effekt på den underliggande adress som pekaren anger.
När en pekartyp konverteras till en annan, om den resulterande pekaren inte är korrekt justerad för den spetsiga typen, är beteendet odefinierat om resultatet avrefereras. I allmänhet är begreppet "korrekt justerat" transitivt: om en pekare till typ A
är korrekt justerad för att en pekare ska skriva B
, som i sin tur är korrekt justerad för att en pekare ska skriva C
, är en pekare att skriva A
korrekt justerad för att en pekare ska skriva C
.
Exempel: Tänk på följande fall där en variabel som har en typ nås via en pekare till en annan 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 }
slutexempel
När en pekartyp konverteras till en pekare till byte
pekaren pekar resultatet på variabelns lägsta adresser byte
. Successiva ökningar av resultatet, upp till variabelns storlek, ger pekare till de återstående byteen för variabeln.
Exempel: Följande metod visar var och en av de åtta byteen i en
double
som ett hexadecimalt värde: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(); } } }
Naturligtvis beror de utdata som produceras på endianitet. En möjlighet är
" BA FF 51 A2 90 6C 24 45"
.slutexempel
Mappningar mellan pekare och heltal är implementeringsdefinierade.
Obs! I 32- och 64-bitars CPU-arkitekturer med ett linjärt adressutrymme fungerar konverteringar av pekare till eller från integraltyper vanligtvis exakt som konverteringar av
uint
respektiveulong
värden till eller från dessa integraltyper. slutkommentar
23.5.2 Pekarmatriser
Matriser med pekare kan konstrueras med hjälp av array_creation_expression (§12.8.17.5) i en osäker kontext. Endast några av de konverteringar som gäller för andra matristyper tillåts på pekarmatriser:
- Den implicita referenskonverteringen (§10.2.8) från alla array_type till
System.Array
och gränssnitten som implementeras gäller även pekarmatriser. Alla försök att komma åt matriselementen viaSystem.Array
eller de gränssnitt som implementeras kan dock resultera i ett undantag vid körning, eftersom pekartyper inte kan konverteras tillobject
. - Implicita och explicita referenskonverteringar (§10.2.8, §10.3.5) från en endimensionell matristyp
S[]
tillSystem.Collections.Generic.IList<T>
och dess allmänna basgränssnitt gäller aldrig för pekarmatriser. - Den explicita referenskonverteringen (§10.3.5) från
System.Array
och de gränssnitt som den implementerar för alla array_type gäller för pekarmatriser. - De explicita referenskonverteringarna (§10.3.5) från
System.Collections.Generic.IList<S>
och dess basgränssnitt till en endimensionell matristypT[]
gäller aldrig för pekarmatriser, eftersom pekartyper inte kan användas som typargument och det inte finns några konverteringar från pekartyper till icke-pekartyper.
Dessa begränsningar innebär att expansionen för -instruktionen foreach
över matriser som beskrivs i §9.4.4.17 inte kan tillämpas på pekarmatriser. I stället visas en foreach
instruktion i formuläret
foreach (V v in x)
embedded_statement
där typen av x
är en matristyp i formuläret T[,,...,]
, n är antalet dimensioner minus 1 och T
eller V
är en pekartyp, expanderas med kapslade for-loopar enligt följande:
{
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*
}
}
}
}
Variablerna a
, i0
, i1
, ... in
är inte synliga för eller tillgängliga för x
eller embedded_statement eller någon annan källkod för programmet. Variabeln v
är skrivskyddad i den inbäddade instruktionen. Om det inte finns någon explicit konvertering (§23.5) från T
(elementtypen) till V
genereras ett fel och inga ytterligare steg vidtas. Om x
har värdet null
genereras en System.NullReferenceException
vid körning.
Obs! Även om pekartyper inte tillåts som typargument kan pekarmatriser användas som typargument. slutkommentar
23.6 Pekare i uttryck
23.6.1 Allmänt
I ett osäkert sammanhang kan ett uttryck ge ett resultat av en pekartyp, men utanför en osäker kontext är det ett kompileringsfel för ett uttryck som är av pekartyp. I exakta termer uppstår ett kompileringsfel utanför en osäker kontext om något simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10) eller element_access (§12.8.12) är av pekartyp.
I ett osäkert sammanhang tillåter produktionerna primary_no_array_creation_expression (§12.8) och unary_expression (§12.9) ytterligare konstruktioner, som beskrivs i följande underkluser.
Obs! Prioriteten och associativiteten för de osäkra operatorerna underförstås av grammatiken. slutkommentar
23.6.2 Indirekt pekare
En pointer_indirection_expression består av en asterisk (*
) följt av en unary_expression.
pointer_indirection_expression
: '*' unary_expression
;
Unary-operatorn *
anger indirekt pekare och används för att hämta variabeln som en pekare pekar till. Resultatet av utvärderingen *P
, där P
är ett uttryck av en pekartyp T*
, är en variabel av typen T
. Det är ett kompileringsfel att tillämpa unary-operatorn *
på ett uttryck av typen void*
eller på ett uttryck som inte är av pekartyp.
Effekten av att tillämpa den unary *
operatorn på en null
-valued pekare är implementeringsdefinierad. I synnerhet finns det ingen garanti för att den här åtgärden genererar en System.NullReferenceException
.
Om ett ogiltigt värde har tilldelats pekaren är beteendet för den odefinierade *
operatorn odefinierat.
Obs! Bland de ogiltiga värdena för att avreferera en pekare av den unary
*
operatorn finns en adress som är olämpligt justerad för den typ som pekas på (se exempel i §23.5) och adressen för en variabel efter slutet av dess livslängd.
Vid en bestämd tilldelningsanalys anses en variabel som framställts genom utvärdering av ett uttryck för formuläret *P
ursprungligen tilldelas (§9.4.2).
23.6.3 Pekarens medlemsåtkomst
En pointer_member_access består av en primary_expression följt av en "->
" token, följt av en identifierare och en valfri type_argument_list.
pointer_member_access
: primary_expression '->' identifier type_argument_list?
;
I en pekare ska medlemsåtkomst i formuläret P->I
P
vara ett uttryck av en pekartyp och I
ska ange en tillgänglig medlem av typen som P
pekar.
En pekarmedlemsåtkomst i formuläret P->I
utvärderas exakt som (*P).I
. En beskrivning av pekarens indirekta operator (*
) finns i §23.6.2. En beskrivning av medlemsåtkomstoperatören (.
) finns i §12.8.7.
Exempel: I följande kod
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()); } } }
operatorn
->
används för att komma åt fält och anropa en metod för en struct via en pekare. Eftersom åtgärdenP->I
exakt motsvarar(*P).I
Main
kan metoden lika väl ha skrivits:class Test { static void Main() { Point point; unsafe { Point* p = &point; (*p).x = 10; (*p).y = 20; Console.WriteLine((*p).ToString()); } } }
slutexempel
23.6.4 Åtkomst till pekarelement
En pointer_element_access består av en primary_no_array_creation_expression följt av ett uttryck som omges av "[
" och "]
".
pointer_element_access
: primary_no_array_creation_expression '[' expression ']'
;
I ett pekarelementsåtkomst i formuläret P[E]
P
ska vara ett uttryck av en annan pekartyp än void*
, och E
ska vara ett uttryck som implicit kan konverteras till int
, uint
, long
eller ulong
.
En pekarelementåtkomst för formuläret P[E]
utvärderas exakt som *(P + E)
. En beskrivning av pekarens indirekta operator (*
) finns i §23.6.2. En beskrivning av pekaretilläggsoperatorn (+
) finns i §23.6.7.
Exempel: I följande kod
class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { p[i] = (char)i; } } } }
en pekarelementåtkomst används för att initiera teckenbufferten i en
for
loop. Eftersom åtgärdenP[E]
exakt motsvarar*(P + E)
kan exemplet lika väl ha skrivits:class Test { static void Main() { unsafe { char* p = stackalloc char[256]; for (int i = 0; i < 256; i++) { *(p + i) = (char)i; } } } }
slutexempel
Operatorn för åtkomst till pekarelementet söker inte efter fel utanför gränserna och beteendet vid åtkomst till ett out-of-bounds-element är odefinierat.
Obs! Detta är samma som C och C++. slutkommentar
23.6.5 Operatorns adress
En addressof_expression består av ett et-tecken (&
) följt av en unary_expression.
addressof_expression
: '&' unary_expression
;
Givet ett uttryck E
som är av en typ T
och klassificeras som en fast variabel (§23.4) beräknar konstruktionen &E
adressen till variabeln som anges av E
. Typen av resultat är T*
och klassificeras som ett värde. Ett kompileringsfel inträffar om E
det inte klassificeras som en variabel, om E
klassificeras som en skrivskyddad lokal variabel eller om E
det anger en flyttbar variabel. I det sista fallet kan en fast instruktion (§23.7) användas för att tillfälligt "åtgärda" variabeln innan den hämtar sin adress.
Obs! Som anges i §12.8.7, utanför en instanskonstruktor eller statisk konstruktor för en struct eller klass som definierar ett
readonly
fält, anses fältet vara ett värde, inte en variabel. Därför går det inte att ta upp adressen. På samma sätt går det inte att ta adressen till en konstant. slutkommentar
Operatorn &
kräver inte att dess argument definitivt tilldelas, men efter en &
åtgärd anses variabeln som operatorn tillämpas på definitivt tilldelas i körningssökvägen där åtgärden inträffar. Det är programmerarens ansvar att se till att en korrekt initiering av variabeln faktiskt sker i denna situation.
Exempel: I följande kod
class Test { static void Main() { int i; unsafe { int* p = &i; *p = 123; } Console.WriteLine(i); } }
i
anses definitivt tilldelad efter den&i
åtgärd som används för att initierap
. Tilldelningen initierar*p
i
i själva verket , men införandet av den här initieringen är programmerarens ansvar och inget kompileringsfel skulle inträffa om tilldelningen togs bort.slutexempel
Obs! Reglerna för bestämd tilldelning för operatorn
&
finns så att redundant initiering av lokala variabler kan undvikas. Många externa API:er pekar till exempel på en struktur som fylls i av API:et. Anrop till sådana API:er skickar vanligtvis adressen till en lokal structvariabel, och utan regeln krävs redundant initiering av structvariabeln. slutkommentar
Obs! När en lokal variabel, värdeparameter eller parametermatris avbildas av en anonym funktion (§12.8.24) anses den lokala variabeln, parametern eller parametermatrisen inte längre vara en fast variabel (§23.7), utan anses i stället vara en flyttbar variabel. Därför är det ett fel för eventuell osäker kod att ta adressen till en lokal variabel, värdeparameter eller parametermatris som har avbildats av en anonym funktion. slutkommentar
23.6.6 Inkrement och minskning av pekare
I ett osäkert sammanhang kan operatorerna och (§12.8.16 och §12.9.6) tillämpas på pekarvariabler av alla typer utom void*
.--
++
För varje pekartyp T*
definieras därför följande operatorer implicit:
T* operator ++(T* x);
T* operator --(T* x);
Operatörerna ger samma resultat som x+1
och x-1
, respektive (§23.6.7). För en pekarvariabel av typen T*
lägger operatorn ++
med andra ord till adressen i variabeln och operatorn --
subtraherar sizeof(T)
från adressen sizeof(T)
i variabeln.
Om en pekaresöknings- eller deskrementsåtgärd flödar över domänen för pekartypen är resultatet implementeringsdefinierat, men inga undantag skapas.
23.6.7 Pekare-aritmetik
I ett osäkert sammanhang kan operatorn +
(§12.10.5) och -
operatorn (§12.10.6) tillämpas på värden för alla pekartyper utom void*
. För varje pekartyp T*
definieras därför följande operatorer implicit:
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);
Givet ett uttryck P
av en pekartyp T*
och ett uttryck N
av typen int
, uint
, long
eller ulong
, uttrycken P + N
och N + P
beräknar pekarvärdet av typen T*
som resulterar från att lägga N * sizeof(T)
till i den adress som anges av P
. På samma sätt beräknar uttrycket P – N
pekarvärdet av typen T*
som resulterar i att subtrahera N * sizeof(T)
från den adress som anges av P
.
Med två uttryck, P
och Q
, av pekartyp T*
, beräknar uttrycket P – Q
skillnaden mellan de adresser som anges av P
och Q
delar sedan den skillnaden sizeof(T)
med . Typen av resultat är alltid long
. I själva verket P - Q
beräknas som ((long)(P) - (long)(Q)) / sizeof(T)
.
Exempel:
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}"); } } }
som producerar utdata:
p - q = -14 q - p = 14
slutexempel
Om en pekare-aritmetikåtgärd flödar över domänen för pekartypen trunkeras resultatet på ett implementeringsdefinierat sätt, men inga undantag skapas.
Jämförelse av 23.6.8 Pekare
I ett osäkert sammanhang kan operatorerna ==
, !=
, <
, >
, <=
och >=
(§12.12) tillämpas på värden för alla pekartyper. Pekarens jämförelseoperatorer är:
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);
Eftersom det finns en implicit konvertering från valfri pekartyp till void*
typen kan operander av valfri pekartyp jämföras med dessa operatorer. Jämförelseoperatorerna jämför de adresser som anges av de två operanderna som om de vore osignerade heltal.
23.6.9 Operatorns storlek
För vissa fördefinierade typer (§12.8.19) ger operatorn sizeof
ett konstant int
värde. För alla andra typer är resultatet av operatorn sizeof
implementeringsdefinierat och klassificeras som ett värde, inte en konstant.
Ordningen i vilken medlemmar packas i en struct är ospecificerad.
I justeringssyfte kan det finnas namnlös utfyllnad i början av en struct, inom en struct och i slutet av structen. Innehållet i de bitar som används som utfyllnad är obestämt.
När det tillämpas på en operand som har structtyp blir resultatet det totala antalet byte i en variabel av den typen, inklusive utfyllnad.
23.7 Den fasta instruktionen
I ett osäkert sammanhang tillåter embedded_statement (§13.1) en ytterligare konstruktion, den fasta instruktionen, som används för att "åtgärda" en flyttbar variabel så att dess adress förblir konstant under instruktionens varaktighet.
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
;
Varje fixed_pointer_declarator deklarerar en lokal variabel för den angivna pointer_type och initierar den lokala variabeln med adressen som beräknas av motsvarande fixed_pointer_initializer. En lokal variabel som deklareras i en fast instruktion är tillgänglig i alla fixed_pointer_initializersom inträffar till höger om variabelns deklaration och i embedded_statement för den fasta instruktionen. En lokal variabel som deklareras av en fast instruktion anses vara skrivskyddad. Ett kompileringsfel uppstår om den inbäddade instruktionen försöker ändra den här lokala variabeln (via tilldelningen ++
eller operatorerna och --
) eller skicka den som en referens- eller utdataparameter.
Det är ett fel att använda en insamlad lokal variabel (§12.19.6.2), värdeparameter eller parametermatris i en fixed_pointer_initializer. Ett fixed_pointer_initializer kan vara något av följande:
- Token "
&
" följt av en variable_reference (§9.5) till en flyttbar variabel (§23.4) av en ohanterad typT
, förutsatt att typenT*
implicit kan konverteras till pekartypen som anges i -instruktionenfixed
. I det här fallet beräknar initiatorn adressen för den angivna variabeln och variabeln är garanterad att finnas kvar på en fast adress under den fasta instruktionens varaktighet. - Ett uttryck för en array_type med element av en ohanterad typ
T
, förutsatt att typenT*
implicit kan konverteras till pekartypen som anges i den fasta instruktionen. I det här fallet beräknar initiatorn adressen för det första elementet i matrisen, och hela matrisen är garanterad att finnas kvar på en fast adress under instruktionensfixed
varaktighet. Om matrisuttrycket ärnull
eller om matrisen har noll element beräknar initieraren en adress som är lika med noll. - Ett uttryck av typen
string
, förutsatt att typenchar*
implicit kan konverteras till pekartypen som anges i -instruktionenfixed
. I det här fallet beräknar initiatorn adressen för det första tecknet i strängen, och hela strängen är garanterad att finnas kvar på en fast adress under instruktionensfixed
varaktighet. Instruktionensfixed
beteende är implementeringsdefinierat om stränguttrycket ärnull
. - Ett uttryck av annan typ än array_type eller
string
, förutsatt att det finns en tillgänglig metod eller tillgänglig tilläggsmetod som matchar signaturenref [readonly] T GetPinnableReference()
, därT
är en unmanaged_type ochT*
implicit kan konverteras till pekartypen som anges i -instruktionenfixed
. I det här fallet beräknar initiatorn adressen för den returnerade variabeln, och den variabeln är garanterad att finnas kvar på en fast adress under instruktionensfixed
varaktighet. EnGetPinnableReference()
metod kan användas av instruktionenfixed
när överbelastningsmatchning (§12.6.4) ger exakt en funktionsmedlem och den funktionsmedlemmen uppfyller föregående villkor. MetodenGetPinnableReference
ska returnera en referens till en adress som är lika med noll, till exempel den som returneras frånSystem.Runtime.CompilerServices.Unsafe.NullRef<T>()
när det inte finns några data att fästa. - En simple_name eller member_access som refererar till en buffertmedlem med fast storlek i en rörlig variabel, förutsatt att typen av buffertmedlem med fast storlek implicit kan konverteras till pekartypen som anges i -instruktionen
fixed
. I det här fallet beräknar initialiseraren en pekare till det första elementet i bufferten med fast storlek (§23.8.3), och bufferten med fast storlek garanteras stanna kvar på en fast adress under instruktionensfixed
varaktighet.
För varje adress som beräknas av en fixed_pointer_initializer säkerställer instruktionen fixed
att variabeln som refereras av adressen inte är föremål för omlokalisering eller bortskaffande av skräpinsamlaren under instruktionens fixed
varaktighet.
Exempel: Om adressen som beräknas av en fixed_pointer_initializer refererar till ett fält i ett objekt eller ett element i en matrisinstans garanterar den fasta instruktionen att den innehållande objektinstansen inte flyttas eller tas bort under instruktionens livslängd. slutexempel
Det är programmerarens ansvar att se till att pekare som skapats av fasta instruktioner inte överlever utöver utförandet av dessa uttalanden.
Exempel: När pekare som skapats av
fixed
-instruktioner skickas till externa API:er är det programmerarens ansvar att se till att API:erna inte behåller något minne från dessa pekare. slutexempel
Fasta objekt kan orsaka fragmentering av heapen (eftersom de inte kan flyttas). Därför bör objekt endast åtgärdas när det är absolut nödvändigt och sedan endast under kortast möjliga tid.
Exempel: Exemplet
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); } } }
visar flera användningsområden för -instruktionen
fixed
. Den första instruktionen korrigerar och hämtar adressen för ett statiskt fält, den andra instruktionen korrigerar och hämtar adressen för ett instansfält och den tredje instruktionen korrigerar och hämtar adressen till ett matriselement. I varje fall skulle det ha varit ett fel att använda den vanliga&
operatorn eftersom variablerna alla klassificeras som flyttbara variabler.Den tredje och fjärde
fixed
instruktionen i exemplet ovan ger identiska resultat. För en matrisinstansa
är det i allmänhet samma sak atta[0]
ange i enfixed
-instruktion som atta
bara ange .slutexempel
I ett osäkert sammanhang lagras matriselement i endimensionella matriser i ökande indexordning, börjar med index 0
och slutar med index Length – 1
. För flerdimensionella matriser lagras matriselement så att indexen för den högra dimensionen ökas först, sedan nästa vänstra dimension och så vidare till vänster.
I en fixed
instruktion som hämtar en pekare p
till en matrisinstans a
, visar pekarvärdena från p
för att p + a.Length - 1
representera adresserna för elementen i matrisen. På samma sätt representerar variablerna som sträcker sig från p[0]
för att p[a.Length - 1]
representera de faktiska matriselementen. Med tanke på hur matriser lagras kan en matris med vilken dimension som helst behandlas som om den vore linjär.
Exempel:
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(); } } } }
som producerar utdata:
[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
slutexempel
Exempel: I följande kod
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); } } }
en
fixed
instruktion används för att åtgärda en matris så att dess adress kan skickas till en metod som tar en pekare.slutexempel
Ett char*
värde som skapas genom att åtgärda en stränginstans pekar alltid på en null-avslutad sträng. I en fast instruktion som hämtar en pekare p
till en stränginstans s
pekarens värden från p
för att representera adresserna för p + s.Length ‑ 1
tecknen i strängen, och pekarvärdet p + s.Length
pekar alltid på ett null-tecken (tecknet med värdet \0).
Exempel:
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); } } }
slutexempel
Exempel: Följande kod visar en fixed_pointer_initializer med ett annat uttryck än array_type eller
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) { // ... } } }
Typen
C
har en tillgängligGetPinnableReference
metod med rätt signatur. I -instruktionen används den som returneras från metoden när den anropasc
för att initiera pekarenint*
p
.ref int
fixed
slutexempel
Om du ändrar objekt av hanterad typ via fasta pekare kan det leda till odefinierat beteende.
Obs! Eftersom strängar till exempel är oföränderliga är det programmerarens ansvar att se till att de tecken som en pekare refererar till till en fast sträng inte ändras. slutkommentar
Obs! Automatisk null-avslutning av strängar är särskilt praktisk när du anropar externa API:er som förväntar sig strängar i "C-stil". Observera dock att en stränginstans tillåts innehålla null-tecken. Om sådana null-tecken finns visas strängen trunkerad när den behandlas som en null-avslutad
char*
. slutkommentar
23,8 buffertar med fast storlek
23.8.1 Allmänt
Buffertar med fast storlek används för att deklarera "C-format" in-line-matriser som medlemmar i structs och är främst användbara för att interagera med ohanterade API:er.
23.8.2 Buffertdeklarationer med fast storlek
En buffert med fast storlek är en medlem som representerar lagring för en buffert med fast längd av variabler av en viss typ. En buffertdeklaration med fast storlek introducerar en eller flera buffertar med fast storlek av en viss elementtyp.
Obs! Precis som en matris kan en buffert med fast storlek betraktas som innehållande element. Därför används även termelementtypen som definierats för en matris med en buffert med fast storlek. slutkommentar
Buffertar med fast storlek tillåts endast i structdeklarationer och kan endast förekomma i osäkra kontexter (§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 ']'
;
En buffertdeklaration med fast storlek kan innehålla en uppsättning attribut (§22), en new
modifierare (§15.3.5), hjälpmedelsmodifierare som motsvarar någon av de deklarerade åtkomstmöjligheter som tillåts för structmedlemmar (§16.4.3) och en unsafe
modifierare (§23.2). Attributen och modifierarna gäller för alla medlemmar som deklareras i buffertdeklarationen med fast storlek. Det är ett fel att samma modifierare visas flera gånger i en buffertdeklaration med fast storlek.
En buffertdeklaration med fast storlek är inte tillåten static
att inkludera modifieraren.
Buffertelementtypen för en buffertdeklaration med fast storlek anger elementtypen för buffertar som infördes i deklarationen. Buffertelementtypen ska vara en av de fördefinierade typerna sbyte
, , byte
, short
, ushort
, int
, uint
, long
, ulong
, char
, float
, , double
eller bool
.
Buffertelementtypen följs av en lista över buffertdeklaratorer med fast storlek, som var och en introducerar en ny medlem. En buffertdeklarator med fast storlek består av en identifierare som namnger medlemmen, följt av ett konstant uttryck som omges [
av och ]
token. Konstantuttrycket anger antalet element i medlemmen som introducerades av buffertdeklaratorn med fast storlek. Typen av konstantuttryck ska implicit konverteras till typen int
, och värdet ska vara ett positivt heltal som inte är noll.
Elementen i en buffert med fast storlek ska anges sekventiellt i minnet.
En buffertdeklaration med fast storlek som deklarerar flera buffertar med fast storlek motsvarar flera deklarationer av en enda buffertdeklaration med fast storlek med samma attribut och elementtyper.
Exempel:
unsafe struct A { public fixed int x[5], y[10], z[100]; }
motsvarar
unsafe struct A { public fixed int x[5]; public fixed int y[10]; public fixed int z[100]; }
slutexempel
23.8.3 Buffertar med fast storlek i uttryck
Medlemssökning (§12.5) för en buffertmedlem med fast storlek fortsätter exakt som medlemssökning i ett fält.
En buffert med fast storlek kan refereras till i ett uttryck med hjälp av en simple_name (§12.8.4), en member_access (§12.8.7) eller en element_access (§12.8.12).
När en buffertmedlem med fast storlek refereras till som ett enkelt namn är effekten samma som en medlemsåtkomst i formuläret this.I
, där I
är buffertmedlemmen med fast storlek.
I en medlemsåtkomst till formuläret E.I
där E.
kan vara implicit this.
, om E
är av en struct-typ och en medlemssökning av I
i den struct-typen identifierar en medlem med fast storlek, utvärderas och klassificeras sedan E.I
enligt följande:
- Om uttrycket
E.I
inte inträffar i en osäker kontext uppstår ett kompileringsfel. - Om
E
klassificeras som ett värde uppstår ett kompileringsfel. - Annars, om
E
är en flyttbar variabel (§23.4) så:- Om uttrycket
E.I
är en fixed_pointer_initializer (§23.7) är resultatet av uttrycket en pekare till det första elementet i buffertmedlemmenI
med fast storlek iE
. - Om uttrycket
E.I
annars är en primary_no_array_creation_expression (§12.8.12.1) inom en element_access (§12.8.12) i formuläretE.I[J]
, så är resultatet avE.I
en pekare,P
, till det första elementet i den fasta storleksbuffertmedlemmenI
iE
, och den omslutande element_access sedan utvärderas som pointer_element_access (§23.6.4)P[J]
. - Annars uppstår ett kompileringsfel.
- Om uttrycket
- Annars
E
refererar en fast variabel och resultatet av uttrycket är en pekare till det första elementet i buffertmedlemmenI
med fast storlek iE
. Resultatet är av typenS*
, där S är elementtypenI
för och klassificeras som ett värde.
Efterföljande element i bufferten med fast storlek kan nås med hjälp av pekaråtgärder från det första elementet. Till skillnad från åtkomst till matriser är åtkomst till elementen i en buffert med fast storlek en osäker åtgärd och är inte intervallkontrollerad.
Exempel: Följande deklarerar och använder en struct med en buffertmedlem med fast storlek.
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); } }
slutexempel
23.8.4 Bestämd tilldelningskontroll
Buffertar med fast storlek är inte föremål för bestämd tilldelningskontroll (§9.4), och buffertmedlemmar med fast storlek ignoreras i syfte att definitivt tilldela variabler av typen struct.
När den yttersta variabeln som innehåller struct för en buffertmedlem med fast storlek är en statisk variabel, en instansvariabel för en klassinstans eller ett matriselement initieras elementen i bufferten med fast storlek automatiskt till standardvärdena (§9.3). I alla andra fall är det ursprungliga innehållet i en buffert med fast storlek odefinierat.
23.9 Stackallokering
Se §12.8.22 för allmän information om operatören stackalloc
. Här diskuteras operatörens förmåga att resultera i en pekare.
I ett osäkert sammanhang om en stackalloc_expression (§12.8.22) inträffar som initieringsuttrycket för en local_variable_declaration (§13.6.2), där local_variable_type antingen är en pekartyp (§23.3) eller härledd (var
), är resultatet av stackalloc_expression en pekare av typen T *
som ska börja på det allokerade blocket, där T
är unmanaged_type av stackalloc_expression.
I alla andra avseenden följer semantiken i local_variable_declarations (§13.6.2) och stackalloc_expression(§12.8.22) i osäkra sammanhang de som definierats för säkra kontexter.
Exempel:
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 }; }
slutexempel
Till skillnad från åtkomst till matriser eller stackalloc
"ed-block av Span<T>
typen" är åtkomst till elementen i ett stackalloc
ed-block av pekartyp en osäker åtgärd och är inte intervallkontrollerad.
Exempel: I följande kod
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)); } }
ett
stackalloc
uttryck används iIntToString
metoden för att allokera en buffert på 16 tecken i stacken. Bufferten ignoreras automatiskt när metoden returneras.Observera dock att
IntToString
kan skrivas om i felsäkert läge, dvs. utan att använda pekare, enligt följande: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(); } }
slutexempel
Slutet på villkorsstyrd normativ text.
ECMA C# draft specification