Nem biztonságos kód, mutatótípusok és függvénymutatók
Az ön által írt C#-kód nagy része "ellenőrizhetően biztonságos kód" .Az ellenőrizhetően biztonságos kód azt jelenti, hogy a .NET-eszközök ellenőrizhetik, hogy a kód biztonságos-e. A biztonságos kód általában nem fér hozzá közvetlenül a memóriához mutatókkal. Emellett nem foglal le nyers memóriát. Ehelyett felügyelt objektumokat hoz létre.
A C# olyan unsafe
környezetet támogat, amelyben ellenőrizhető kódot írhat. unsafe
Egy környezetben a kód használhat mutatókat, lefoglalhat és felszabadíthat memóriablokkokat, és függvénymutatókat használó metódusokat hívhat meg. A C# nem biztonságos kódja nem feltétlenül veszélyes; csak olyan kód, amelynek biztonságát nem lehet ellenőrizni.
A nem biztonságos kód a következő tulajdonságokkal rendelkezik:
- A metódusok, típusok és kódblokkok nem biztonságosként definiálhatók.
- Bizonyos esetekben a nem biztonságos kód növelheti az alkalmazások teljesítményét a tömbkorlát-ellenőrzések eltávolításával.
- Nem biztonságos kódra van szükség, ha olyan natív függvényeket hív meg, amelyek mutatót igényelnek.
- A nem biztonságos kód használata biztonsági és stabilitási kockázatokat jelent.
- A nem biztonságos blokkokat tartalmazó kódot az AllowUnsafeBlocks fordítóbeállítással kell lefordítani.
Mutatótípusok
Nem biztonságos környezetben a típus lehet mutatótípus, az értéktípuson kívül vagy hivatkozástípus is. A mutatótípus-deklaráció az alábbi űrlapok egyikét használja:
type* identifier;
void* identifier; //allowed but not recommended
A mutatótípus előtt *
megadott típust hivatkozástípusnak nevezzük. Csak a nem felügyelt típus lehet hivatkozási típus.
A mutatótípusok nem öröklődnek az objektumtól, és nem léteznek átalakítások a mutatótípusok és a object
mutatótípusok között. Emellett a boxolás és a kicsomagolás nem támogatja a mutatókat. A különböző mutatótípusok, valamint a mutatótípusok és az integráltípusok között azonban konvertálható.
Ha ugyanabban a deklarációban több mutatót deklarál, a csillagot (*
) csak az alapul szolgáló típussal együtt kell írnia. Nem minden mutatónév előtagjaként használatos. Példa:
int* p1, p2, p3; // Ok
int *p1, *p2, *p3; // Invalid in C#
A mutató nem mutathat hivatkozásra vagy hivatkozásokat tartalmazó szerkezetre , mert az objektumhivatkozások akkor is szemétként gyűjthetők, ha egy mutató erre mutat. A szemétgyűjtő nem követi nyomon, hogy egy objektumra mutató mutató típusok mutatnak-e.
A mutató típusú MyType*
változó értéke egy típusváltozó MyType
címe. Az alábbiakban példákat láthat a mutatótípus-deklarációkra:
int* p
:p
egy egész számra mutató mutató.int** p
:p
egy egész számra mutató mutató.int*[] p
:p
az egész számokra mutató mutatók egydimenziós tömbje.char* p
:p
egy karakterre mutató mutató.void* p
:p
ismeretlen típusra mutató mutató.
A mutató indirekt operátora *
a mutatóváltozó által mutatott helyen lévő tartalom eléréséhez használható. Vegyük például a következő deklarációt:
int* myVariable;
A kifejezés *myVariable
a int
megadott címen myVariable
található változót jelöli.
Az utasításban szereplő cikkekben számos példa mutat.fixed
Az alábbi példa a kulcsszót unsafe
és az utasítást fixed
használja, és bemutatja, hogyan lehet növelni a belső mutatót. Ezt a kódot beillesztheti egy konzolalkalmazás fő függvényébe a futtatáshoz. Ezeket a példákat az AllowUnsafeBlocks fordítókészlettel kell lefordítani.
// Normal pointer to an object.
int[] a = [10, 20, 30, 40, 50];
// Must be in unsafe code to use interior pointers.
unsafe
{
// Must pin object on heap so that it doesn't move while using interior pointers.
fixed (int* p = &a[0])
{
// p is pinned as well as object, so create another pointer to show incrementing it.
int* p2 = p;
Console.WriteLine(*p2);
// Incrementing p2 bumps the pointer by four bytes due to its type ...
p2 += 1;
Console.WriteLine(*p2);
p2 += 1;
Console.WriteLine(*p2);
Console.WriteLine("--------");
Console.WriteLine(*p);
// Dereferencing p and incrementing changes the value of a[0] ...
*p += 1;
Console.WriteLine(*p);
*p += 1;
Console.WriteLine(*p);
}
}
Console.WriteLine("--------");
Console.WriteLine(a[0]);
/*
Output:
10
20
30
--------
10
11
12
--------
12
*/
Az indirekt operátor nem alkalmazható egy ilyen típusú void*
mutatóra. Az üres mutatót azonban bármely más típusú mutatóvá alakíthatja át, és fordítva.
A mutató lehet null
. Ha a közvetett operátort null mutatóra alkalmazza, az implementáció által definiált viselkedést okoz.
Ha mutatót ad át a metódusok között, az meghatározatlan viselkedést okozhat. Fontolja meg azt a metódust, amely egy helyi változóra mutató mutatót ad vissza egy in
, out
vagy paraméteren ref
keresztül, vagy a függvény eredményeként. Ha az egérmutató rögzített blokkban volt beállítva, előfordulhat, hogy a változó, amelyre mutat, többé nem lesz javítva.
Az alábbi táblázat felsorolja azokat az operátorokat és utasításokat, amelyek nem biztonságos környezetben működnek a mutatókon:
Operátor/utasítás | Használat |
---|---|
* |
Indirekt mutatót hajt végre. |
-> |
Egy struktúra egy tagját egy mutatón keresztül éri el. |
[] |
Indexel egy mutatót. |
& |
Egy változó címét szerzi be. |
++ és -- |
Növekmények és csökkenő mutatók. |
+ és - |
Mutató aritmetikai műveleteket végez. |
== , != , < , > , <= és >= |
Összehasonlítja a mutatókat. |
stackalloc |
Memóriát foglal le a veremen. |
fixed Nyilatkozatot |
Ideiglenesen kijavít egy változót, hogy a címe megtalálható legyen. |
A mutatóval kapcsolatos operátorokról további információt a Mutatóval kapcsolatos operátorok című témakörben talál.
Bármely mutatótípus implicit módon átalakítható típussá void*
. Az érték null
bármely mutatótípushoz hozzárendelhető. Bármely mutatótípus explicit módon bármely más mutatótípussá alakítható át egy öntött kifejezés használatával. Bármely integráltípust átalakíthat mutatótípussá, vagy bármely mutatótípust integráltípussá. Ezek az átalakítások explicit leadást igényelnek.
Az alábbi példa egy int*
byte*
. Figyelje meg, hogy a mutató a változó legalacsonyabb címzett bájtjára mutat. Ha egymás után növeli az eredményt, akár a (4 bájtos) méretig int
, megjelenítheti a változó fennmaradó bájtját.
int number = 1024;
unsafe
{
// Convert to byte:
byte* p = (byte*)&number;
System.Console.Write("The 4 bytes of the integer:");
// Display the 4 bytes of the int variable:
for (int i = 0 ; i < sizeof(int) ; ++i)
{
System.Console.Write(" {0:X2}", *p);
// Increment the pointer:
p++;
}
System.Console.WriteLine();
System.Console.WriteLine("The value of the integer: {0}", number);
/* Output:
The 4 bytes of the integer: 00 04 00 00
The value of the integer: 1024
*/
}
Rögzített méretű pufferek
A kulcsszóval fixed
egy rögzített méretű tömböt tartalmazó puffert hozhat létre egy adatstruktúrában. A rögzített méretű pufferek akkor hasznosak, ha olyan metódusokat ír, amelyek más nyelvekből vagy platformokról származó adatforrásokkal működnek együtt. A rögzített méretű puffer bármilyen attribútumot vagy módosító tulajdonságot képes átvenni, amely a normál strukturált tagok számára engedélyezett. Az egyetlen korlátozás az, hogy a tömbtípusnak bool
byte
, , , char
, short
, long
int
sbyte
, , ushort
, uint
, ulong
float
double
, vagy .
private fixed char name[30];
A biztonságos kódban a tömböt tartalmazó C# szerkezet nem tartalmazza a tömbelemeket. A szerkezet ehelyett az elemekre mutató hivatkozást tartalmaz. Ha nem biztonságos kódblokkban használják, rögzített méretű tömböt ágyazhat be egy szerkezetbe.
A következők struct
mérete nem függ a tömb elemeinek számától, mivel pathName
ez egy hivatkozás:
public struct PathArray
{
public char[] pathName;
private int reserved;
}
A szerkezetek nem biztonságos kódba ágyazott tömböt tartalmazhatnak. Az alábbi példában a fixedBuffer
tömb mérete rögzített. Egy utasítással fixed
mutatót kap az első elemhez. Ezen a mutatón keresztül érheti el a tömb elemeit. Az fixed
utasítás a példánymezőt fixedBuffer
egy adott helyre rögzíti a memóriában.
internal unsafe struct Buffer
{
public fixed char fixedBuffer[128];
}
internal unsafe class Example
{
public Buffer buffer = default;
}
private static void AccessEmbeddedArray()
{
var example = new Example();
unsafe
{
// Pin the buffer to a fixed location in memory.
fixed (char* charPtr = example.buffer.fixedBuffer)
{
*charPtr = 'A';
}
// Access safely through the index:
char c = example.buffer.fixedBuffer[0];
Console.WriteLine(c);
// Modify through the index:
example.buffer.fixedBuffer[0] = 'B';
Console.WriteLine(example.buffer.fixedBuffer[0]);
}
}
A 128 elemtömb char
mérete 256 bájt. A rögzített méretű karakterpufferek karakterenként mindig 2 bájtot vesznek igénybe, a kódolástól függetlenül. Ez a tömbméret akkor is megegyezik, ha a karakterpufferek API-metódusokhoz vannak rendezve, vagy azokkal vagy CharSet = CharSet.Ansi
.CharSet = CharSet.Auto
További információ: CharSet.
Az előző példa a mezők rögzítés nélküli elérését fixed
mutatja be. Egy másik gyakori rögzített méretű tömb a bool tömb. A tömb elemei bool
mindig 1 bájt méretűek. bool
a tömbök nem alkalmasak bittömbök vagy pufferek létrehozására.
A rögzített méretű pufferek a System.Runtime.CompilerServices.UnsafeValueTypeAttributekövetkezővel vannak lefordítva, ami arra utasítja a közös nyelvi futtatókörnyezetet (CLR), hogy egy típus egy nem felügyelt tömböt tartalmaz, amely esetleg túlcsordulhat. A stackalloc használatával lefoglalt memória automatikusan engedélyezi a puffertúllépés észlelését a CLR-ben. Az előző példa bemutatja, hogyan létezhet rögzített méretű puffer egy unsafe struct
.
internal unsafe struct Buffer
{
public fixed char fixedBuffer[128];
}
A fordító által létrehozott C# a Buffer
következőképpen van attribútummal elosztva:
internal struct Buffer
{
[StructLayout(LayoutKind.Sequential, Size = 256)]
[CompilerGenerated]
[UnsafeValueType]
public struct <fixedBuffer>e__FixedBuffer
{
public char FixedElementField;
}
[FixedBuffer(typeof(char), 128)]
public <fixedBuffer>e__FixedBuffer fixedBuffer;
}
A rögzített méretű pufferek a következő módokon térnek el a normál tömböktől:
- Csak kontextusban
unsafe
használható. - Csak a szerkezetek példánymezői lehetnek.
- Ezek mindig vektorok, vagy egydimenziós tömbök.
- A deklarációnak tartalmaznia kell a hosszt, például
fixed char id[8]
. Nem használhatófixed char id[]
.
Mutató használata bájtok tömbjének másolásához
Az alábbi példa mutatókkal másol bájtokat az egyik tömbből a másikba.
Ez a példa a nem biztonságos kulcsszót használja, amely lehetővé teszi a mutatók használatát a Copy
metódusban. A rögzített utasítás a forrás- és céltömbök mutatóinak deklarálásához használható. Az fixed
utasítás rögzíti a forrás- és céltömbök helyét a memóriában, hogy ne helyezhesse át őket szemétgyűjtés. A tömbök memóriablokkjai a blokk befejezésekor fixed
nem lesznek rögzítve. Mivel a példában szereplő Copy
metódus a kulcsszót unsafe
használja, az AllowUnsafeBlocks fordítóval kell lefordítani.
Ez a példa a második nem felügyelt mutató helyett indexekkel fér hozzá mindkét tömb elemeihez. Az és pTarget
a pSource
mutatók deklarációja rögzíti a tömböket.
static unsafe void Copy(byte[] source, int sourceOffset, byte[] target,
int targetOffset, int count)
{
// If either array is not instantiated, you cannot complete the copy.
if ((source == null) || (target == null))
{
throw new System.ArgumentException("source or target is null");
}
// If either offset, or the number of bytes to copy, is negative, you
// cannot complete the copy.
if ((sourceOffset < 0) || (targetOffset < 0) || (count < 0))
{
throw new System.ArgumentException("offset or bytes to copy is negative");
}
// If the number of bytes from the offset to the end of the array is
// less than the number of bytes you want to copy, you cannot complete
// the copy.
if ((source.Length - sourceOffset < count) ||
(target.Length - targetOffset < count))
{
throw new System.ArgumentException("offset to end of array is less than bytes to be copied");
}
// The following fixed statement pins the location of the source and
// target objects in memory so that they will not be moved by garbage
// collection.
fixed (byte* pSource = source, pTarget = target)
{
// Copy the specified number of bytes from source to target.
for (int i = 0; i < count; i++)
{
pTarget[targetOffset + i] = pSource[sourceOffset + i];
}
}
}
static void UnsafeCopyArrays()
{
// Create two arrays of the same length.
int length = 100;
byte[] byteArray1 = new byte[length];
byte[] byteArray2 = new byte[length];
// Fill byteArray1 with 0 - 99.
for (int i = 0; i < length; ++i)
{
byteArray1[i] = (byte)i;
}
// Display the first 10 elements in byteArray1.
System.Console.WriteLine("The first 10 elements of the original are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray1[i] + " ");
}
System.Console.WriteLine("\n");
// Copy the contents of byteArray1 to byteArray2.
Copy(byteArray1, 0, byteArray2, 0, length);
// Display the first 10 elements in the copy, byteArray2.
System.Console.WriteLine("The first 10 elements of the copy are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray2[i] + " ");
}
System.Console.WriteLine("\n");
// Copy the contents of the last 10 elements of byteArray1 to the
// beginning of byteArray2.
// The offset specifies where the copying begins in the source array.
int offset = length - 10;
Copy(byteArray1, offset, byteArray2, 0, length - offset);
// Display the first 10 elements in the copy, byteArray2.
System.Console.WriteLine("The first 10 elements of the copy are:");
for (int i = 0; i < 10; ++i)
{
System.Console.Write(byteArray2[i] + " ");
}
System.Console.WriteLine("\n");
/* Output:
The first 10 elements of the original are:
0 1 2 3 4 5 6 7 8 9
The first 10 elements of the copy are:
0 1 2 3 4 5 6 7 8 9
The first 10 elements of the copy are:
90 91 92 93 94 95 96 97 98 99
*/
}
Függvénymutatók
A C# típusokat biztosít delegate
a biztonságos függvénymutató-objektumok definiálásához. A meghatalmazott meghívása magában foglalja a metódusból System.Delegate származtatott típus példányosítását, és virtuális metódus hívását Invoke
. Ez a virtuális hívás az IL utasítást callvirt
használja. A teljesítmény szempontjából kritikus kódelérési utaknál hatékonyabb az calli
IL-utasítás használata.
Függvénymutatót a delegate*
szintaxissal határozhat meg. A fordító az utasítással hívja meg a függvényt delegate
az calli
objektum példányosítása és hívása Invoke
helyett. Az alábbi kód két metódust deklarál, amelyek két delegate
azonos típusú objektumot kombinálnak egy vagy egy delegate*
használatával. Az első metódus delegált típust System.Func<T1,T2,TResult> használ. A második módszer ugyanazokkal a paraméterekkel és visszatérési típussal rendelkező deklarációt használ delegate*
:
public static T Combine<T>(Func<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static T UnsafeCombine<T>(delegate*<T, T, T> combinator, T left, T right) =>
combinator(left, right);
Az alábbi kód bemutatja, hogyan deklarálhat egy statikus helyi függvényt, és hogyan hívhatja meg a UnsafeCombine
metódust az adott helyi függvényre mutató mutatóval:
static int localMultiply(int x, int y) => x * y;
int product = UnsafeCombine(&localMultiply, 3, 4);
Az előző kód a függvénymutatóként elért függvény számos szabályát szemlélteti:
- A függvénymutatók csak kontextusban
unsafe
deklarálhatók. delegate*
A (vagy visszaadottdelegate*
) metódusokat csak kontextusbanunsafe
lehet meghívni.- A
&
függvény címét beolvasni kívánt operátor csak függvényekenstatic
engedélyezett. (Ez a szabály a tagfüggvényekre és a helyi függvényekre is vonatkozik).
A szintaxis párhuzamot mutat a deklarálási delegate
típusokkal és a mutatókkal. A *
deklaráció utótagja delegate
egy függvénymutató. A &
metóduscsoport függvénymutatóhoz rendelése azt jelzi, hogy a művelet a metódus címét veszi át.
Megadhatja a hívási konvenciót delegate*
a kulcsszavak managed
és unmanaged
a . Emellett a függvénymutatók esetében unmanaged
megadhatja a hívási konvencióciót. Az alábbi deklarációk példákat mutatnak mindegyikre. Az első deklaráció a hívási managed
konvenciót használja, amely az alapértelmezett. A következő négy egy hívási konvenciót unmanaged
használ. Mindegyik az ECMA 335 hívási konvenciók egyikét adja meg: Cdecl
, Stdcall
vagy Thiscall
Fastcall
. Az utolsó deklaráció a hívási unmanaged
konvenciót használja, amely arra utasítja a CLR-t, hogy válassza ki a platform alapértelmezett hívási konvenciót. A CLR futásidőben választja ki a hívási konvenciót.
public static T ManagedCombine<T>(delegate* managed<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static T CDeclCombine<T>(delegate* unmanaged[Cdecl]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static T StdcallCombine<T>(delegate* unmanaged[Stdcall]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static T FastcallCombine<T>(delegate* unmanaged[Fastcall]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static T ThiscallCombine<T>(delegate* unmanaged[Thiscall]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static T UnmanagedCombine<T>(delegate* unmanaged<T, T, T> combinator, T left, T right) =>
combinator(left, right);
A függvénymutatókról a Függvénymutató funkció specifikációjában olvashat bővebben.
C# nyelvspecifikáció
További információ: A C# nyelv specifikációjának nem biztonságos kód fejezete.