Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Merparten av C#-koden som du skriver är verifierbart säker kod. Verifierbart säker kod innebär att .NET verktyg kan kontrollera att koden är säker. I allmänhet kommer säker kod inte direkt åt minnet med hjälp av pekare. Den allokerar inte heller råminne. Det skapar hanterade objekt i stället.
C#-språkreferensen dokumenterar den senaste versionen av C#-språket. Den innehåller även inledande dokumentation för funktioner i offentliga förhandsversioner för den kommande språkversionen.
Dokumentationen identifierar alla funktioner som först introducerades i de tre senaste versionerna av språket eller i aktuella offentliga förhandsversioner.
Tips/Råd
Information om när en funktion först introducerades i C# finns i artikeln om språkversionshistoriken för C#.
C# stöder också en unsafe kontext där du kan skriva icke-verifierad kod. Osäker kod är inte nödvändigtvis farlig. det är bara kod vars säkerhet inte kan verifieras av .NET verktyg. Du använder osäker kod för att anropa inbyggda funktioner som kräver pekare och i vissa fall för att förbättra prestandan genom direkt minnesåtkomst som undviker matrisbundna kontroller. Osäker kod medför också säkerhets- och stabilitetsrisker. Om du vill kompilera kod som innehåller en unsafe kontext lägger du till kompileringsalternativet AllowUnsafeBlocks .
C# definierar två modeller för vad som räknas som osäker kod: den ursprungliga modellen och en uppdaterad minnessäkerhetsmodell som är i förhandsversion i C# 15 och .NET 11. Information om hur de två modellerna skiljer sig åt finns i Två modeller för osäker kod.
Information om metodtips för osäker kod i C# finns i Metodtips för osäker kod.
Två modeller för osäker kod
C# definierar två modeller för osäker kod. Modellen avgör i själva verket vilka åtgärder som kräver en unsafe kontext och hur unsafe modifieraren på en medlem påverkar anropare.
-
Ursprunglig osäker modell: Kontexten
unsafeomfattar förekomsten av pekarfunktioner. Du deklarerar en pekartyp, tar adressen till en variabel, avrefererar en pekare, konverterar ettstackallocuttryck till en pekare eller tillämparsizeofendast på en godtycklig typ i enunsafekontext. (Ettstackallocuttryck som tilldelats enSpan<T>ellerReadOnlySpan<T>tillåts i säker kod.) Modifierarenunsafeför en typ, en medlem eller ett block upprättar den kontexten men lägger ingen skyldighet på uppringare. C# 1.0 introducerade den här modellen, och den är fortfarande standard. -
Uppdaterad minnessäkerhetsmodell: Kontexten
unsafeomfattar de åtgärder som kommer åt minnet som körningen inte hanterar. Förekomsten av en pekare är inte osäker. avreferensen för en pekare är. Modifierarenunsafepå en medlem blir ett kontrakt som sprider skyldigheten att granska säkerheten till anroparen. Den här modellen finns i förhandsversion i C# 15 och .NET 11.
I följande tabell jämförs vilka åtgärder som kräver en unsafe kontext i varje modell.
| Verksamhet | Ursprunglig modell | Uppdaterad modell |
|---|---|---|
Deklarera en pekartyp eller ta en adress med & |
Kräver unsafe |
Tillåts i säker kod |
Påståendet fixed |
Kräver unsafe |
Tillåts i säker kod |
Konvertera ett stackalloc uttryck till en pekare |
Kräver unsafe |
Tillåts i säker kod |
Operatorn sizeof för alla ohanterade typer |
Kräver unsafe |
Tillåts i säker kod |
Indirekt pekare (*p), medlemsåtkomst (p->m) eller elementåtkomst (p[i]) |
Kräver unsafe |
Kräver unsafe |
| Anrop till funktionspekare | Kräver unsafe |
Kräver unsafe |
| Elementåtkomst på en buffert med fast storlek | Kräver unsafe |
Kräver unsafe |
Anropa en medlem markerad unsafe |
Inget krav på uppringare | Kräver unsafe |
Om du vill prova den uppdaterade modellen använder du .NET 11 SDK (i förhandsversion) och ställer in kompilatoralternativet LangVersion på preview. Pekaravslappningen gäller när du kompilerar med C# 15-kompilatorn och preview språkversionen. Det fullständiga verkställandet, inklusive uppringarens skyldigheter och församlingens opt-in, är fortfarande under utveckling. Mer information finns i Den uppdaterade minnessäkerhetsmodellen (förhandsversion).
Den ursprungliga osäkra modellen
I den ursprungliga modellen etablerar nyckelordet unsafe en osäker kontext för en typ, en medlem eller ett block, och den kontexten låser upp pekarfunktionerna som beskrivs i följande avsnitt. Modifieraren ändrar bara vad den markerade koden kan göra. Den unsafe ställer inga krav på anropare. Om du vill kompilera något av dessa exempel anger du kompileringsalternativet AllowUnsafeBlocks .
Pekartyper
I ett osäkert sammanhang kan en typ vara en pekartyp, förutom en värdetyp eller en referenstyp. En deklaration av pekartyp har något av följande formulär:
type* identifier;
void* identifier; //allowed but not recommended
Den typ som du anger innan * i en pekartyp är referenstypen.
Pekartyper ärver inte från objektet och det finns inga konverteringar mellan pekartyper och object. Boxning och avboxning stöder inte heller pekare. Du kan dock konvertera mellan olika pekartyper och mellan pekartyper och integraltyper.
När du deklarerar flera pekare i samma deklaration skriver du asterisken (*) tillsammans med endast den underliggande typen. Den används inte som ett prefix för varje pekarnamn. Till exempel:
int* p1, p2, p3; // Ok
int *p1, *p2, *p3; // Invalid in C#
Skräpinsamlaren håller inte reda på om ett objekt pekas på av några pekartyper. Om referensen är ett objekt i den hanterade heapen (inklusive lokala variabler som fångas av lambda-uttryck eller anonyma ombud) måste du fästa objektet så länge pekaren används.
Värdet för pekarvariabeln av typen MyType* är adressen till en variabel av typen MyType. Följande är exempel på deklarationer av pekartyp:
-
int* p:pär en pekare till ett heltal. -
int** p:pär en pekare till en pekare till ett heltal. -
int*[] p:pär en endimensionell matris med pekare till heltal. -
char* p:pär en pekare till ett tecken. -
void* p:pär en pekare till en okänd typ.
Du kan använda pekarens indirekta operator * för att komma åt innehållet på den plats som pekarvariabeln pekar på. Tänk till exempel på följande deklaration:
int* myVariable;
Uttrycket *myVariable anger variabeln int som finns på adressen i myVariable.
Det finns flera exempel på pekare i artiklarna om fixed-instruktionen. I följande exempel används nyckelordet unsafe och instruktionen fixed och visar hur du ökar en inre pekare. Du kan klistra in den här koden i huvudfunktionen i ett konsolprogram för att köra den. Dessa exempel måste kompileras med kompilatoralternativet AllowUnsafeBlocks inställt.
// 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
*/
Du kan inte använda indirektionsoperatorn på en pekare av typen void*. Du kan dock använda en cast för att konvertera en void-pekare till vilken annan pekartyp som helst, och vice versa.
En pekare kan vara null. Om du tillämpar indirektionsoperatorn på en nullpekare uppstår ett implementeringsdefinierat beteende.
Att skicka pekare mellan metoder kan orsaka odefinierat beteende. Överväg en metod som returnerar en pekare till en lokal variabel via en in, outeller ref parameter eller som funktionsresultat. Om pekaren har angetts i ett fast block kan variabeln som den pekar på inte längre vara fast.
I följande tabell visas de operatorer och instruktioner som kan användas på pekare i en osäker kontext:
| Operator/instruktion | Använd |
|---|---|
* |
Utför pekarindirektion. |
-> |
Öppnar en medlem i en struct via en pekare. |
[] |
Indexerar en pekare. |
& |
Hämtar adressen för en variabel. |
++ och -- |
Inkrements- och minskningspekare. |
+ och - |
Utför pekararitmetik. |
==, !=, <, >, <=och >= |
Jämför pekare. |
stackalloc |
Allokerar minne på stacken. |
fixed-instruktion |
Korrigerar tillfälligt en variabel så att dess adress kan hittas. |
Mer information om pekarrelaterade operatorer finns i Pointer-relaterade operatorer.
Alla pekartyper kan implicit konverteras till en void* typ. Alla pekartyper kan tilldelas värdet null. Du kan uttryckligen konvertera valfri pekartyp till valfri annan pekartyp med hjälp av ett gjutet uttryck. Du kan också konvertera valfri integraltyp till en pekartyp eller valfri pekartyp till en integrerad typ. Dessa konverteringar kräver en explicit gjutning.
I följande exempel konverteras en int* till en byte*. Observera att pekaren pekar på variabelns lägsta adresserade byte. När du stegvis ökar resultatet, upp till storleken på int (4 byte), kan du visa de återstående byteen i variabeln.
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: {number}");
/* Output:
The 4 bytes of the integer: 00 04 00 00
The value of the integer: 1024
*/
}
Buffertar med fast storlek
Matriser är referenstyper, så i säker kod lagrar ett structfält som är en matris endast en referens till matrisens element, inte själva elementen. Storleken på följande struct beror inte på antalet element i matrisen, eftersom pathName är en referens:
public struct PathArray
{
public char[] pathName;
private int reserved;
}
Om du vill lagra matrisens innehåll i själva structen använder du nyckelordet fixed för att deklarera en buffert med fast storlek. Nyckelordet fixed kräver en unsafe kontext. Buffertar med fast storlek är användbara när du skriver metoder som samverkar med datakällor från andra språk eller plattformar. En buffert med fast storlek kan ta alla attribut eller modifierare som tillåts för vanliga struct-medlemmar. Den enda begränsningen är att matristypen måste vara bool, byte, char, short, int, long, , sbyte, uintushort, ulong, , floateller double:
private fixed char name[30];
I följande exempel har matrisen fixedBuffer en fast storlek. Du använder en fixed instruktion för att hämta en pekare till det första elementet och sedan komma åt elementen i matrisen via den pekaren. Instruktionen fixed fäster instansfältet på fixedBuffer en specifik plats i minnet:
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]);
}
}
Storleken på 128-elementet char matris är 256 byte. Fast storleks tecken buffert tar alltid 2 byte per tecken, oavsett kodning. Den här matrisstorleken är densamma även när teckenbuffertar konverteras till API-metoder eller structs med CharSet = CharSet.Auto eller CharSet = CharSet.Ansi. Mer information finns i CharSet.
Föregående exempel visar åtkomst till fixed-fält utan att låsa. En annan vanlig matris med fast storlek är matrisen bool. Elementen i en bool matris är alltid 1 byte i storlek.
bool matriser är inte lämpliga för att skapa bitmatriser eller buffertar.
Buffertar med fast storlek kompileras med System.Runtime.CompilerServices.UnsafeValueTypeAttribute, som instruerar CLR (Common Language Runtime) att en typ innehåller en ohanterad matris som potentiellt kan flöda över. Minne som allokeras med stackalloc möjliggör också automatiskt funktioner för identifiering av buffertöverskridning i CLR. Föregående exempel visar hur en buffert med fast storlek kan finnas i en unsafe struct.
internal unsafe struct Buffer
{
public fixed char fixedBuffer[128];
}
Den kompilatorgenererade C#-filen för Buffer tillskrivs på följande sätt:
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;
}
Buffertar med fast storlek skiljer sig från vanliga matriser på följande sätt:
- Du kan bara använda dem i en
unsafekontext. - De kan bara vara instansfält för structs.
- De är alltid vektorer eller endimensionella matriser.
- Deklarationen måste innehålla längden, till exempel
fixed char id[8]. Du kan inte användafixed char id[].
Funktionspekare
C# innehåller delegate typer för att definiera säkra funktionspekarobjekt. Att anropa en delegat innebär att instansiera en typ som härleds från System.Delegate och göra ett anrop till dess virtuella Invoke-metod. Det här virtuella anropet använder instruktionen callvirt IL. I prestandakritiska kodsökvägar är det mer effektivt att använda calli IL-instruktionen.
Du kan definiera en funktionspekare med hjälp av syntaxen delegate* . Kompilatorn anropar funktionen med hjälp av instruktionen calli i stället för att instansiera ett delegate objekt och anropa Invoke. Följande kod deklarerar två metoder som använder en delegate eller en delegate* för att kombinera två objekt av samma typ. Den första metoden använder en System.Func<T1,T2,TResult> ombudstyp. Den andra metoden använder en delegate*-deklaration med samma parametrar och returtyp:
public static T Combine<T>(Func<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static unsafe T UnsafeCombine<T>(delegate*<T, T, T> combinator, T left, T right) =>
combinator(left, right);
Följande kod visar hur du deklarerar en statisk lokal funktion och anropar metoden med hjälp av en pekare till den UnsafeCombine lokala funktionen:
int product = 0;
unsafe
{
static int localMultiply(int x, int y) => x * y;
product = UnsafeCombine(&localMultiply, 3, 4);
}
Föregående kod illustrerar flera av reglerna för funktionen som används som en funktionspekare:
- Du kan bara deklarera funktionspekare i en
unsafekontext. - Du kan bara anropa metoder som tar en
delegate*(eller returnerar endelegate*) i enunsafekontext. - Operatorn
&för att hämta adressen till en funktion tillåts endast förstaticfunktioner. Den här regeln gäller både medlemsfunktioner och lokala funktioner.
Syntaxen har paralleller med att deklarera delegate typer och använda pekare. Suffixet * på delegate anger att deklarationen är en funktionspekare. Värdet & när du tilldelar en metodgrupp till en funktionspekare indikerar att operationen tar metodens adress.
Du kan ange anropskonventionen för en delegate* med hjälp av nyckelorden managed och unmanaged. För unmanaged funktionspekare kan du dessutom ange anropskonventionen. Följande deklarationer visar exempel på var och en. Den första deklarationen använder managed-anropskonventionen, som är standard. De följande fyra använder en unmanaged anropskonvention. Var och en anger någon av ECMA 335-anropskonventionerna: Cdecl, Stdcall, Fastcalleller Thiscall. Den senaste deklarationen använder unmanaged anropande konvention och instruerar CLR att välja standardanropskonventionen för plattformen. CLR väljer anropskonventionen vid körningstid.
public static unsafe T ManagedCombine<T>(delegate* managed<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static unsafe T CDeclCombine<T>(delegate* unmanaged[Cdecl]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static unsafe T StdcallCombine<T>(delegate* unmanaged[Stdcall]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static unsafe T FastcallCombine<T>(delegate* unmanaged[Fastcall]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static unsafe T ThiscallCombine<T>(delegate* unmanaged[Thiscall]<T, T, T> combinator, T left, T right) =>
combinator(left, right);
public static unsafe T UnmanagedCombine<T>(delegate* unmanaged<T, T, T> combinator, T left, T right) =>
combinator(left, right);
Du kan lära dig mer om funktionspekare i avsnittet Funktionspekare i C#-språkspecifikationen.
Exempel: Använd pekare för att kopiera en matris med byte
I följande exempel används pekare för att kopiera byte från en matris till en annan.
I det här exemplet används nyckelordet unsafe , som gör att du kan använda pekare i Copy metoden. -instruktionen fixed deklarerar pekare till käll- och målmatriserna. Instruktionen fixedfäster platsen för käll- och målmatriserna i minnet så att skräpinsamlingen inte flyttar matriserna. Blocket fixed fäster minnesblocken för matriserna i blockets omfång.
Copy Eftersom metoden i det här exemplet använder nyckelordet unsafe måste du kompilera det med hjälp av kompileringsalternativet AllowUnsafeBlocks.
Det här exemplet kommer åt elementen i båda matriserna med hjälp av index i stället för en andra ohanterad pekare. Deklarationen av pSource och pTarget pekare fäster matriserna.
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
*/
}
Den uppdaterade minnessäkerhetsmodellen (förhandsversion)
Important
Den uppdaterade minnessäkerhetsmodellen är en förhandsversionsfunktion i C# 15 och .NET 11. Den fortsätter att utvecklas baserat på feedback under förhandsversionerna. Om du vill prova modellen använder du SDK:et .NET 11 (förhandsversion) och ställer in kompilatoralternativet LangVersion på preview. Kompilatorn i .NET 11 Förhandsversion 5 implementerar pekaravslappningen men tillämpar ännu inte uppringarens skyldigheter, sammansättningens anmälning eller nyckelordetsafe. Fullständig design finns i specifikationen för minnessäkerhetsfunktioner.
Den uppdaterade modellen separerar två saker som den ursprungliga modellen behandlar som en: förekomsten av pekarkod och spridningen av säkerhetsskyldigheter till anropare. Att markera en medlem unsafe tillåter inte längre bara pekare i kroppen, det gör medlemsanroparen osäker, så varje uppringare måste antingen sprida den skyldigheten eller släppa den bakom en validerad, säker anropsbar gräns. För att stödja den separationen begränsar modellen även den osäkra kontexten: förekomsten av en pekare är inte osäker, bara de åtgärder som kommer åt minnet som körningen inte hanterar. Med smalning kan du hålla, skicka och returnera pekare i säker kod, samtidigt unsafe som du markerar de åtgärder och medlemmar som faktiskt kan bryta mot minnessäkerheten.
Medlemmar som är osäkra på uppringaren
I den ursprungliga modellen unsafe tillåter modifieraren på en medlem endast pekare i medlemmens signatur och brödtext. Det informerar inte uppringare om säkerhet. Den uppdaterade modellen ger modifieraren betydelse för anropare. När du markerar en medlem unsafebehandlar kompilatorn den som uppringaren osäker (kallas även kräver osäker): varje anropare måste anropa den från en unsafe kontext och skyldigheten att granska säkerheten flyttas till den anroparen.
Modifieraren unsafe för en medlemssignatur etablerar inte längre en osäker kontext för brödtexten. De två rollerna delas:
- Modifieraren
unsafepå signaturen sprider skyldigheten till uppringare. - Ett inre
unsafeblock omfattar de åtgärder som har åtkomst till ohanterat minne.
I följande förhandsgranskningsförhandsversion ReadInt32 är anroparen osäker. Signaturen unsafe bär modifieraren och ett inre unsafe block omsluter avreferensen:
// Preview: illustrates the updated model, which the current compiler doesn't fully enforce yet.
public static unsafe int ReadInt32(byte* source)
{
unsafe
{
return *(int*)source;
}
}
En anropare omsluter anropet i sitt eget unsafe block:
// Preview
unsafe
{
int value = ReadInt32(buffer);
}
Den uppdaterade modellen skärper också några relaterade regler:
- Modifieraren
unsafegenererar ett fel på en typdeklaration, en statisk konstruktor och en finalator, eftersom modifieraren inte har någon anropare att informera. - Ombud kan inte vara
unsafe, eftersom ett ombud är typformat. - En typ vars parameterlösa konstruktor är
unsafeuppfyller inte villkoretnew().
Åtgärder som kräver en osäker kontext
Åtgärder som har åtkomst till det riktade minnet kräver en unsafe kontext:
- Indirekt pekare (
*p), åtkomst till pekarmedlem (p->member) och åtkomst till pekarelement (p[i]). - Anrop till funktionspekare.
- Elementåtkomst på en buffert med fast storlek.
I följande exempel fästs en matris utan kontext unsafe , men pekaren avrefereras inuti en:
public static int ReadValue(int[] numbers)
{
fixed (int* first = numbers)
{
// Dereferencing a pointer accesses unmanaged memory, so it still
// requires an unsafe context.
unsafe
{
return *first;
}
}
}
Avslappnade åtgärder
Åtgärder som inte har åtkomst till spetsigt minne kräver inte längre en unsafe kontext:
- Deklarera en pekartyp och ta adressen till en variabel med operatorn
&. - Instruktionen
fixedsom fäster en variabel. - Konvertera ett
stackallocuttryck till en pekare. - Operatorn
sizeoftillämpades på alla ohanterade typer.
I följande exempel skapas och fästs pekare utan kontext unsafe :
public static void CreatePointer()
{
int value = 42;
// Creating a pointer doesn't require an unsafe context.
int* pointer = &value;
int** pointerToPointer = &pointer;
}
public static void PinArray(int[] numbers)
{
// The fixed statement no longer requires an unsafe context.
fixed (int* first = numbers)
{
int* current = first;
}
}
Dessa avkopplingar gäller när du kompilerar med preview språkversionen, oavsett om en sammansättning väljer de uppdaterade minnessäkerhetsreglerna eller inte.
Ansvarsfrihet för uppringarens osäkra skyldigheter
En medlem som anropar en uppringare-osäker åtgärd har två alternativ: sprida förpliktelsen eller bevilja den.
-
Sprid: Markera din egen medlem
unsafe. Skyldigheten övergår till dina uppringare. Använd spridning när du inte kan verifiera förpliktelsen helt själv. -
Urladdning: Lämna medlemmens signatur säker. Verifiera skyldigheten i medlemmen, vanligtvis med runtime-vakter, och utför sedan den osäkra åtgärden i ett inre
unsafeblock. En medlem som innehåller ett inreunsafeblock men inte markerar sin egen signaturunsafeär en osäker gräns: den omvandlar osäker kod till en säker anropsbar yta.
Följande förhandsgranskningsförhandsversion verifierar dess indata med ett skydd, fäster en hanterad matris och läser igenom pekaren. Anropare behöver unsafe ingen kontext, eftersom metoden befriar skyldigheten:
// Preview
public static int SumBytes(byte[] source)
{
ArgumentNullException.ThrowIfNull(source);
fixed (byte* first = source)
{
unsafe
{
// SAFETY: the null check and source.Length bound every read to the pinned array.
int total = 0;
for (int i = 0; i < source.Length; i++)
{
total += first[i];
}
return total;
}
}
}
Null-kontrollen och matrislängden utesluter de indata som skulle låta en läsning köras förbi bufferten, så avreferensen unsafe i blocket är ljud. Metoden lämnar ingen kvarvarande skyldighet, så den exponerar en säker anropsbar signatur.
Säkerhetsdokumentation
En uppringare som är osäker bör dokumentera vad anroparen måste garantera. Den uppdaterade modellen uppmuntrar till två kompletterande kommentarsformat:
- Ett
/// <safety>dokumentationsblock ovanför signaturen anger det formella kontraktet: de villkor som en uppringare måste uppfylla. En analysator kan flagga en medlem som är osäker på uppringaren och som saknar en. - En
// SAFETY:kommentar i ettunsafeblock registrerar varför åtgärden är sund på den platsen, för de utvecklare och granskare som läser brödtexten.
Följande förhandsgranskningsförhandsversion visar båda formaten på en anropare-osäker ReadByte metod:
// Preview
/// <summary>Reads a single byte from unmanaged memory.</summary>
/// <safety>
/// The sum of <paramref name="ptr"/> and <paramref name="offset"/> must address a byte
/// the caller is permitted to read.
/// </safety>
public static unsafe byte ReadByte(IntPtr ptr, int offset)
{
byte* address = (byte*)ptr;
unsafe
{
// SAFETY: relies on the caller obligation stated in the <safety> block.
return address[offset];
}
}
Blocket /// <safety> meddelar dig kontraktet. Kontraktet tillhör den dokumentation där varje uppringare och granskare ser det.
Osäkra fält
unsafe Använd modifieraren för ett fält när dess deklarerade typ inte uttrycker kontrakt som den omslutande typen underhåller och annan kod är beroende av. Osäkerheten finns i klyftan mellan vad typsystemet ser och vad typen lovar. Modifieraren tvingar varje skrivning till fältet till ett unsafe block, vilket håller skrivningar granskningsbara på ett ställe.
Det tydligaste fallet är ett fält som innehåller en inbyggd pekare. Pekaren deklarerar inte hur många byte den adresserar som en System.Span<T> gör, så den innehållande typen underhåller själva informationen:
// Preview
public class NativeBuffer
{
/// <safety>
/// Null, or points to a buffer of Length bytes.
/// </safety>
private unsafe byte* _pointer;
public int Length { get; }
public byte ReadAt(int index)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, Length);
unsafe
{
// SAFETY: the bounds checks confine the read to the buffer that _pointer addresses.
return _pointer[index];
}
}
}
Ett readonly unsafe fält parar ihop kontraktet med ett inbyggt skydd: unsafe namnger invarianten och readonly förhindrar en skrivning som kan bryta det efter konstruktionen. Om du markerar en egenskap eller en händelse unsafe blir det inte osäkert för uppringaren av bakgrundsfältet. I en struct med [StructLayout(LayoutKind.Explicit)]markerar du varje fält antingen safe eller unsafe.
Det säkra nyckelordet
Den uppdaterade modellen lägger till ett safe kontextuellt nyckelord som intygar att en deklaration är ljud där kompilatorn kräver att du gör valet explicit.
En extern medlem anropar inbyggt kod så att kompilatorn inte kan klassificera sin säkerhet. Under den uppdaterade modellen markerar du varje extern deklaration, inklusive en LibraryImport partiell metod, antingen safe eller unsafe:
// Preview
[LibraryImport("libc")]
internal static safe partial int getpid();
[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial nint strlen(byte* str);
getpid tar inga parametrar och returnerar en primitiv, så författaren intygar att samtalet är säkert och anropare använder det utan ceremoni.
strlen tar en rå pekare som den interna koden avrefererar, så deklarationen är unsafe och sprider skyldigheten till anropare. Att utelämna båda modifierarna är ett fel, vilket tvingar dig att fatta säkerhetsbeslutet. Ett fält i en struct med explicit layout använder samma regel.
Beteende för opt-in och cross-assembly
Den uppdaterade modellen har två oberoende växlar på projektnivå:
- En ny opt-in-egenskap aktiverar de uppdaterade reglerna. När egenskapen är avstängd gäller de ursprungliga reglerna. När den är på
unsafesprids en medlem till anropare och kompilatorn registrerar valet i sammansättningen med MemorySafetyRulesAttribute attributet . - Den befintliga egenskapen AllowUnsafeBlocks portar varje utseende av nyckelordet
unsafe, inklusive de inre blocken på anropsplatser. Standardvärdet ärfalse, så att ett projekt som standard inte kan anropa något osäkert API.
De två egenskaperna kombineras på följande sätt:
| Egenskap för anmälning | AllowUnsafeBlocks |
Result |
|---|---|---|
| På | Av (förvalt) | Den säkraste konfigurationen. Projektet använder den uppdaterade modellen och tillåter ingen osäker kod. |
| På | På | Projektet använder den uppdaterade modellen och tillåter osäker kod. |
| Off | Off | Den ursprungliga modellen gäller och projektet kan inte använda pekartyper. |
| Off | På | Den ursprungliga modellen gäller och projektet kan använda pekartyper. |
Om en sammansättning tillämpar de uppdaterade reglerna mot en annan beror på vilken sida som väljer:
-
Uppringare för uppdaterad modell, uppringare av uppdaterad modell: Anroparens markörer färdas
unsafegenom metadata. Anroparen omsluter varje anrop till en uppringare-osäker medlem i ettunsafeblock. -
Uppringare med uppdaterad modell, anropare för originalmodell: Ett kompatibilitetsläge behandlar alla samtalsmedlemmar med en pekartyp i sin signatur som anropare-osäkra, så samtalswebbplatsen behöver ett omslutande
unsafeblock. Det här läget hindrar ett pekarbaserat API från att tyst förlora sinaunsafekrav. - Anropare för ursprunglig modell, anropare för uppdaterad modell: De ursprungliga pekarreglerna gäller fortfarande. En uppringare-osäker medlem som inte har någon pekartyp i sin signatur blir anropsbar från säker kod, eftersom den ursprungliga modellens anropare inte kan läsa de nya markörer.
Språkspecifikation för C#
Mer information finns i Osäker kod kapitel i C#-språkspecifikationen.
Information om hur du utformar den uppdaterade minnessäkerhetsmodellen finns i specifikationen för minnessäkerhetsfunktionen.