Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Yazdığınız C# kodunun çoğu doğrulanabilir şekilde güvenli bir koddur. Verifiably safe code, .NET araçlarının kodun güvenli olduğunu doğrulayabildiği anlamına gelir. Genel olarak, güvenli kod işaretçileri kullanarak belleğe doğrudan erişmez. Ayrıca ham bellek ayırmaz. Bunun yerine yönetilen nesneler oluşturur.
C# dili başvuru belgesi, C# dilinin en son yayımlanan sürümünü gösterir. Ayrıca, yaklaşan dil sürümü için genel önizlemelerdeki özelliklere yönelik ilk belgeleri içerir.
Belgelerde ilk olarak dilin son üç sürümünde veya geçerli genel önizlemelerde sunulan tüm özellikler tanımlanır.
Tavsiye
Bir özelliğin C# dilinde ilk tanıtıldığı zamanları bulmak için C# dil sürümü geçmişi makalesine bakın.
C# ayrıca, doğrulamaz kod yazabileceğiniz bir unsafe bağlamı da destekler. Güvenli olmayan kod her zaman tehlikeli değildir; yalnızca güvenliği .NET araçlar tarafından doğrulanabilen bir kod. İşaretçi gerektiren yerel işlevleri çağırmak ve bazı durumlarda dizi sınırları denetimlerini önleyen doğrudan bellek erişimi aracılığıyla performansı geliştirmek için güvenli olmayan kod kullanırsınız. Güvenli olmayan kod, güvenlik ve kararlılık risklerini de getirir. Bağlam unsafe içeren kodu derlemek için AllowUnsafeBlocks derleyici seçeneğini ekleyin.
C# güvenli olmayan kod olarak sayan iki model tanımlar: özgün model ve C# 15 ve .NET 11'de önizlemede olan güncelleştirilmiş bir bellek güvenliği modeli. İki modelin arasındaki farklar hakkında bilgi için bkz. Güvenli olmayan kod için iki model.
C# dilinde güvenli olmayan kod için en iyi yöntemler hakkında bilgi için bkz. Güvenli olmayan kod en iyi yöntemleri.
Güvenli olmayan kod için iki model
C# güvenli olmayan kod için iki model tanımlar. Model, hangi işlemlerin bağlam gerektirdiğini unsafe ve bir üyedeki değiştiricinin unsafe çağıranları nasıl etkilediğini belirler.
-
Özgün güvenli olmayan model: Bağlam,
unsafeişaretçi özelliklerinin varlığını kapsar. İşaretçi türü bildirir, değişkenin adresini alır, işaretçi başvurusundan çıkarılır, birstackallocifade işaretçiye dönüştürülür veya yalnızca bağlamunsafeiçinde rastgele bir türe uygulanırsizeof. (Güvenlistackallockodda veyaReadOnlySpan<T>öğesine atanan birSpan<T>ifadeye izin verilir.) Birunsafetür, üye veya blok üzerindeki değiştirici bu bağlamı oluşturur ancak arayanlara hiçbir yükümlülük getirmez. C# 1.0 bu modeli kullanıma sunar ve varsayılan olarak kalır. -
Güncelleştirilmiş bellek güvenliği modeli: Bağlam,
unsafeçalışma zamanının yönetmediği belleğe erişen işlemleri kapsar. bir işaretçinin varlığı güvenli değildir; bir işaretçinin başvurusunun kaldırılıyor olmasıdır. Birunsafeüyedeki değiştirici, arayana güvenliği denetleme yükümlülüğünü yayılımlayan bir sözleşme haline gelir. Bu model C# 15 ve .NET 11'de önizleme aşamasındadır.
Aşağıdaki tabloda, her modelde bağlam unsafe gerektiren işlemler karşılaştırılmaktadır.
| Operation | Özgün model | Güncelleştirilmiş model |
|---|---|---|
İşaretçi türü bildirme veya ile adres alma & |
Gerektirir unsafe |
Güvenli kodda izin verilir |
fixed ifadesi |
Gerektirir unsafe |
Güvenli kodda izin verilir |
İfadeyi stackalloc işaretçiye dönüştürme |
Gerektirir unsafe |
Güvenli kodda izin verilir |
sizeof Yönetilmeyen herhangi bir türdeki işleç |
Gerektirir unsafe |
Güvenli kodda izin verilir |
İşaretçi dolaylı (*p), üye erişimi (p->m) veya öğe erişimi (p[i]) |
Gerektirir unsafe |
Gerektirir unsafe |
| İşlev işaretçisi çağırma | Gerektirir unsafe |
Gerektirir unsafe |
| Sabit boyutlu arabellekte öğe erişimi | Gerektirir unsafe |
Gerektirir unsafe |
İşaretli bir üyeyi çağırma unsafe |
Arayan gereksinimi yok | Gerektirir unsafe |
Güncelleştirilmiş modeli denemek için .NET 11 SDK'sını LangVersion (önizlemede) kullanın ve derleyici seçeneğini olarak previewayarlayın. İşaretçi rahatlamaları, C# 15 derleyicisi ve preview dil sürümüyle derlediğiniz her zaman uygulanır. Arayan yükümlülükleri ve derleme kabul etme dahil olmak üzere tam uygulama hala geliştirme aşamasındadır. Daha fazla bilgi için bkz . Güncelleştirilmiş bellek güvenliği modeli (önizleme).
Özgün güvenli olmayan model
Özgün modelde unsafe anahtar sözcüğü bir tür, üye veya blok üzerinde güvenli olmayan bir bağlam oluşturur ve bu bağlam aşağıdaki bölümlerde açıklanan işaretçi özelliklerinin kilidini açar. Değiştirici unsafe yalnızca işaretli kodun yapabileceklerini değiştirir; arayanlara hiçbir gereksinim yapmaz. Bu örneklerden herhangi birini derlemek için AllowUnsafeBlocks derleyici seçeneğini ayarlayın.
İşaretçi türleri
Güvenli olmayan bir bağlamda tür, değer türüne veya başvuru türüne ek olarak işaretçi türü olabilir. İşaretçi türü bildirimi aşağıdaki formlardan birini alır:
type* identifier;
void* identifier; //allowed but not recommended
İşaretçi türünden * önce belirttiğiniz tür , başvuran türüdür.
İşaretçi türleri nesneden devralmaz ve işaretçi türleri ile objectarasında hiçbir dönüştürme yoktur. Ayrıca, kutulama ve kutu açma işaretçileri desteklemez. Ancak, farklı işaretçi türleri arasında ve işaretçi türleri ile integral türleri arasında dönüştürebilirsiniz.
Aynı bildirimde birden çok işaretçi bildirdiğinizde, yıldız işareti (*) yalnızca temel türle birlikte yazın. İşaretçi adlarının hiçbirine ön ek olarak kullanılmaz. Mesela:
int* p1, p2, p3; // Ok
int *p1, *p2, *p3; // Invalid in C#
Çöp toplayıcı, herhangi bir işaretçi türü tarafından bir nesnenin işaret edilip edilmediğini izlemez. Başvuran yönetilen yığındaki bir nesneyse (lambda ifadeleri veya anonim temsilciler tarafından yakalanan yerel değişkenler dahil), işaretçi kullanıldığı sürece nesneyi sabitlemeniz gerekir.
MyType* türündeki işaretçi değişkeninin değeri, MyTypetüründe bir değişkenin adresidir. İşaretçi türü bildirimlerine örnekler aşağıda verilmiştir:
-
int* p:pbir tamsayı işaretçisidir. -
int** p:p, tamsayı işaretçisine yönelik bir işaretçidir. -
int*[] p:p, tamsayılara yönelik tek boyutlu bir işaretçi dizisidir. -
char* p:pbir karakter işaretçisidir. -
void* p:pbilinmeyen bir türe yönelik bir işaretçidir.
İşaretçi dolaylı işlecini * kullanarak işaretçi değişkeninin işaret ettiği konumdaki içeriklere erişebilirsiniz. Örneğin, aşağıdaki bildirimi göz önünde bulundurun:
int* myVariable;
*myVariable ifadesi, intiçinde bulunan adreste bulunan myVariable değişkenini belirtir.
fixed
ifadesindeki makalelerde birkaç işaretçi örneği vardır. Aşağıdaki örnek, unsafe anahtar sözcüğünü ve fixed deyimini kullanır ve iç işaretçinin nasıl artırılıp artırılamını gösterir. Bu kodu çalıştırmak için bir konsol uygulamasının Main işlevine yapıştırabilirsiniz. Bu örnekler, AllowUnsafeBlocks derleyici seçenek kümesiyle derlenmelidir.
// 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
*/
void*türünde bir işaretçiye dolaylı işleci uygulayamazsınız. Ancak, geçersiz işaretçiyi başka bir işaretçi türüne dönüştürmek için bir atama kullanabilirsiniz ve tam tersi de geçerlidir.
İşaretçi nullolabilir. Dolaylı işlecini null işaretçiye uygulamak, uygulama tanımlı bir davranışa neden olur.
İşaretçilerin yöntemler arasında geçirilmesi tanımsız davranışa neden olabilir. bir in, outveya ref parametresi aracılığıyla ya da işlev sonucu olarak yerel bir değişkene işaretçi döndüren bir yöntem düşünün. İşaretçi sabit bir blokta ayarlandıysa, işaret ettiği değişken artık düzeltilmeyebilir.
Aşağıdaki tabloda, güvenli olmayan bir bağlamda işaretçiler üzerinde çalışabilen işleçler ve deyimler listelenmiştir:
| İşleç/İfade | Kullan |
|---|---|
* |
İşaretçi yönlendirmesi gerçekleştirir. |
-> |
Bir işaretçi aracılığıyla bir yapının üyesine erişir. |
[] |
İşaretçiyi dizine ekler. |
& |
Bir değişkenin adresini alır. |
++ ve -- |
İşaretçileri artırır ve azaltır. |
+ ve - |
İşaretçi aritmetiği gerçekleştirir. |
==, !=, <, >, <=ve >= |
İşaretçileri karşılaştırır. |
stackalloc |
Yığın üzerinde bellek ayırır. |
fixed ifade |
Bir değişkeni, adresinin bulunabilmesi için geçici olarak düzeltir. |
İşaretçiyle ilgili işleçler hakkında daha fazla bilgi için bkz. İşaretçiyle ilgili işleçler.
Herhangi bir işaretçi türü örtük olarak bir void* türüne dönüştürülebilir. herhangi bir işaretçi türüne nulldeğeri atanabilir. Bir atama ifadesi kullanarak herhangi bir işaretçi türünü başka bir işaretçi türüne açıkça dönüştürebilirsiniz. Herhangi bir integral türünü bir işaretçi türüne veya herhangi bir işaretçi türünü bir integral türüne de dönüştürebilirsiniz. Bu dönüştürmeler açık tür dönüşümü gerektirir.
Aşağıdaki örnek, bir int*byte*dönüştürür. İşaretçinin değişkenin adreslenen en düşük baytını işaret ettiğini göreceksiniz. Sonucu art arda artırdığınızda, int boyutuna kadar (4 bayt), değişkenin kalan baytlarını görüntüleyebilirsiniz.
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
*/
}
Sabit boyutlu arabellekler
Diziler başvuru türleridir, bu nedenle güvenli kodda, dizi olan bir yapı alanı yalnızca dizinin öğelerine başvuru depolar, öğelerin kendileri depolar. Aşağıdakilerin struct boyutu dizideki öğelerin sayısına bağlı değildir, çünkü pathName bir başvurudur:
public struct PathArray
{
public char[] pathName;
private int reserved;
}
Dizinin içeriğini yapının içinde depolamak için anahtar sözcüğünü fixed kullanarak sabit boyutlu bir arabellek bildirin. anahtar fixed sözcüğü bir unsafe bağlam gerektirir. Sabit boyutlu arabellekler, diğer dil veya platformlardan veri kaynaklarıyla birlikte çalışabilen yöntemler yazdığınızda kullanışlıdır. Sabit boyutlu arabellek, normal yapı üyeleri için izin verilen tüm öznitelikleri veya değiştiricileri alabilir. Tek kısıtlama, dizi türünün , , , , , short, int, , long, sbyte, uintfloatushortulongveya doubleolmasıdır:boolcharbyte
private fixed char name[30];
Aşağıdaki örnekte, fixedBuffer dizisi sabit bir boyuta sahiptir. bir deyimini kullanarakfixed ilk öğeye bir işaretçi alırsınız ve ardından bu işaretçi aracılığıyla dizinin öğelerine erişirsiniz. deyimi örnek fixed alanını bellekteki fixedBuffer belirli bir konuma sabitler:
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]);
}
}
Dizi char 128 öğesinin boyutu 256 bayttır. Sabit boyutlu karakter arabellekleri kodlamadan bağımsız olarak her zaman karakter başına 2 bayt alır. Bu dizi boyutu, char tamponları CharSet = CharSet.Auto veya CharSet = CharSet.Ansiile API metodlarına veya yapılara sıralandığında bile aynıdır. Daha fazla bilgi için bkz. CharSet.
Yukarıdaki örnekte, sabitleme olmadan fixed alanlara erişim gösterilmektedir. Sabit boyutlu bir diğer yaygın dizi de bool dizisidir. bir bool dizisindeki öğelerin boyutu her zaman 1 bayttır.
bool diziler bit dizileri veya arabellekler oluşturmak için uygun değildir.
Sabit boyutlu arabellekler, System.Runtime.CompilerServices.UnsafeValueTypeAttributeile derlenir; bu, ortak dil çalışma zamanına (CLR) bir türün taşma olasılığı olan yönetilmeyen bir dizi içerdiğini belirtir.
Stackalloc kullanılarak ayrılan bellek, CLR'de arabellek taşması algılama özelliklerini de otomatik olarak etkinleştirir. Yukarıdaki örnekte, içinde sabit boyutlu bir arabelleğin nasıl bulunabileceği unsafe structgösterilmektedir.
internal unsafe struct Buffer
{
public fixed char fixedBuffer[128];
}
Buffer için derleyici tarafından oluşturulan C# aşağıdaki gibi özniteliklendirilir:
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;
}
Sabit boyutlu arabellekler aşağıdaki yollarla normal dizilerden farklıdır:
- Bunları yalnızca bir
unsafebağlamda kullanabilirsiniz. - Bunlar yalnızca yapıların örnek alanları olabilir.
- Bunlar her zaman vektör veya tek boyutlu dizilerdir.
- Bildirimin uzunluğu içermesi gerekir, örneğin
fixed char id[8].fixed char id[]kullanamazsınız.
İşlev işaretçileri
C# güvenli işlev işaretçisi nesnelerini tanımlamak için delegate türler sağlar. Temsilci çağırmak, System.Delegate'den türetilmiş bir türün örneğini oluşturmayı ve onun Invoke sanal yöntemini çağırmayı içerir. Bu sanal çağrı callvirt IL yönergesini kullanır. Performans açısından kritik kod yollarında calli IL yönergesini kullanmak daha verimlidir.
Söz dizimini kullanarak işlev işaretçisi delegate* tanımlayabilirsiniz. Derleyici, bir calli nesnesinin örneğini oluşturmak ve çağırmak yerine yönergesini delegate kullanarak işlevini çağırırInvoke. Aşağıdaki kod, aynı türdeki iki nesneyi birleştirmek için bir delegate veya delegate* kullanan iki yöntem bildirir. İlk yöntem bir System.Func<T1,T2,TResult> temsilci türü kullanır. İkinci yöntem, aynı parametrelere ve dönüş türüne sahip bir delegate* bildirimi kullanır:
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);
Aşağıdaki kod, statik bir yerel işlevi nasıl bildirdiğiniz ve bu yerel işlevin UnsafeCombine işaretçisini kullanarak yöntemini nasıl çağırabileceğinizi gösterir:
int product = 0;
unsafe
{
static int localMultiply(int x, int y) => x * y;
product = UnsafeCombine(&localMultiply, 3, 4);
}
Yukarıdaki kod, işlev işaretçisi olarak erişilen işlevdeki kuralların birkaçını gösterir:
- İşlev işaretçilerini yalnızca bir
unsafebağlamda bildirebilirsiniz. - Yalnızca bağlam içinde
delegate*birdelegate*alan (veya döndürenunsafe) yöntemleri çağırabilirsiniz. - bir işlevin adresini almak için
&işlecine yalnızcastaticişlevlerde izin verilir. Bu kural hem üye işlevleri hem de yerel işlevler için geçerlidir.
Söz dizimi, delegate türlerini bildirme ve işaretçileri kullanma ile paraleldir.
* soneki, delegate üzerindeki bildirimin işlev işaretçisiolduğunu gösterir. bir işlev işaretçisine yöntem grubu atarken &, işlemin yöntemin adresini aldığını gösterir.
ve delegate*anahtar sözcüklerini managed kullanarak için unmanaged çağırma kuralını belirtebilirsiniz. Ayrıca, unmanaged işlev işaretçileri için çağırma kuralını belirtebilirsiniz. Aşağıdaki bildirimler her birinin örneklerini gösterir. İlk bildirim, varsayılan olan managed çağırma kuralını kullanır. Sonraki dördü bir unmanaged çağırma kuralı kullanır. Her biri ECMA 335 çağrı kurallarından birini belirtir: Cdecl, Stdcall, Fastcallveya Thiscall. Son bildirimde, CLR'ye platform için varsayılan çağrı kuralını seçmesi talimatını veren unmanaged çağırma kuralı kullanılır. CLR, çalışma zamanında çağırma kuralını seçer.
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);
C# dil belirtiminin İşlev işaretçileri bölümünde işlev işaretçileri hakkında daha fazla bilgi edinebilirsiniz.
Örnek: Bayt dizisini kopyalamak için işaretçileri kullanma
Aşağıdaki örnek, bir diziden diğerine bayt kopyalamak için işaretçileri kullanır.
Bu örnekte, yönteminde unsafe işaretçileri Copy kullanmanıza olanak tanıyan anahtar sözcüğü kullanılır. deyimi, fixed kaynak ve hedef dizilere yönelik işaretçileri bildirir.
fixed deyimi çöp toplamanın dizileri taşımaması için bellekteki kaynak ve hedef dizilerin konumunu sabitler. blok, fixed blok kapsamındaki diziler için bellek bloklarını sabitler. Bu örnekteki Copy yöntem anahtar sözcüğünü kullandığından unsafe , AllowUnsafeBlocks derleyici seçeneğini kullanarak derlemeniz gerekir.
Bu örnek, ikinci bir yönetilmeyen işaretçi yerine dizinleri kullanarak her iki dizinin öğelerine erişir.
pSource ve pTarget işaretçilerinin bildirimi dizileri sabitler.
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
*/
}
Güncelleştirilmiş bellek güvenliği modeli (önizleme)
Important
Güncelleştirilmiş bellek güvenliği modeli, C# 15 ve .NET 11'deki bir önizleme özelliğidir. Önizleme sürümleri sırasında geri bildirimler temelinde gelişmeye devam eder. Modeli denemek için .NET 11 (önizleme) SDK'sını LangVersion kullanın ve derleyici seçeneğini olarak previewayarlayın. .NET 11 Preview 5'teki derleyici işaretçi gevşetmelerini uygular ancak çağıran yükümlülüklerini, derleme kabul etme veya safe anahtar sözcüğünü henüz zorunlu kılmaz. Tam tasarım için bkz. bellek güvenliği özelliği belirtimi.
Güncelleştirilmiş model, özgün modelin tek bir şey olarak değerlendirdiği iki şeyi ayırır: işaretçi kodunun varlığı ve arayanlara güvenlik yükümlülüklerinin yayılması . Bir üyenin unsafe işaretlenmesi artık yalnızca gövdesinde işaretçilere izin vermiyor; üye çağıranı güvensiz hale getiriyor, bu nedenle her arayan bu yükümlülüğü yaymalı veya doğrulanmış, güvenli çağrılabilen bir sınır arkasında boşaltmalıdır. Model bu ayrımı desteklemek için güvenli olmayan bağlamı da daraltıyor: bir işaretçinin varlığı güvenli değil, yalnızca çalışma zamanının yönetmediği belleğe erişen işlemler. Daraltma, güvenli kodda işaretçileri tutmanıza, geçirmenize ve döndürmenize olanak tanırken unsafe , bellek güvenliğini gerçekten ihlal edebilen işlemleri ve üyeleri işaretler.
Arayan-güvenli olmayan üyeler
Özgün modelde, üyedeki unsafe değiştirici yalnızca üyenin imzasında ve gövdesinde işaretçilere izin verir. Arayanları güvenlik hakkında bilgilendirmez. Güncelleştirilmiş model, arayanlar için değiştirici anlamı verir. Bir üyeyi unsafeişaretlediğinizde, derleyici onu çağıran-güvenli değil ( gerekli-güvensiz olarak da adlandırılır) olarak değerlendirir: her çağıranın bunu bir unsafe bağlamdan çağırması gerekir ve güvenliği denetleme yükümlülüğü bu çağırana taşınır.
Üye unsafe imzadaki değiştirici artık gövde için güvenli olmayan bir bağlam oluşturmaz. İki rol bölünür:
- İmzadaki
unsafedeğiştirici, arayanlara yükümlülüğü yayıyor. - İç
unsafeblok, yönetilmeyen belleğe erişen işlemlerin kapsamını belirler.
Aşağıdaki önizleme mockup'ında ReadInt32 arayan-güvenli değildir. İmza değiştiriciyi unsafe taşır ve bir iç unsafe blok başvuruyu sarmalar:
// 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;
}
}
Çağıran, çağrıyı kendi unsafe bloğuna sarmalar:
// Preview
unsafe
{
int value = ReadInt32(buffer);
}
Güncelleştirilmiş model, ilgili birkaç kuralı da sıkılaştırır:
-
unsafeDeğiştirici, bir tür bildiriminde, statik oluşturucuda ve sonlandırıcıda bir hata oluşturur çünkü değiştiricinin bilgilendirecek bir çağıranı yoktur. - Temsilci tür şeklinde olduğundan temsilci olamaz
unsafe. - Parametresiz oluşturucusunun
unsafekısıtlamayınew()karşılamadığı bir tür.
Güvenli olmayan bağlam gerektiren işlemler
İşaret edilen belleğe erişen işlemler için bir unsafe bağlam gerekir:
- İşaretçi dolaylı (
*p), işaretçi üyesi erişimi ()p->memberve işaretçi öğesi erişimi (p[i]). - İşlev işaretçisi çağırma.
- Sabit boyutlu arabellekte öğe erişimi.
Aşağıdaki örnek, bağlam olmadan unsafe bir diziyi sabitler ancak işaretçiyi bir dizinin içine başvurur:
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;
}
}
}
Gevşek işlemler
İşaret edilen belleğe erişmeyen işlemler için artık bağlam unsafe gerekmez:
- bir işaretçi türü bildirme ve işleç ile bir değişkenin
&adresini alma. - Bir
fixeddeğişkeni sabitleyen deyim. - İfadeyi
stackallocişaretçiye dönüştürme. - İşleç
sizeofyönetilmeyen herhangi bir türe uygulanır.
Aşağıdaki örnek, bağlam unsafe olmadan işaretçiler oluşturur ve sabitler:
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;
}
}
Bu rahatlamalar, bir derlemenin preview güncelleştirilmiş bellek güvenliği kurallarını kabul edip etmediğinden bağımsız olarak dil sürümüyle derlediğiniz her durumda geçerlidir.
Terhis çağıran-güvenli olmayan yükümlülükler
Arayan-güvenli olmayan işlemi çağıran bir üyenin iki seçeneği vardır: yükümlülüğü yaymak veya boşaltmak.
-
Yay: Kendi üyenizi
unsafeişaretleyin. Bu yükümlülük arayanlarınıza geçer. Yükümlülüğü kendiniz tam olarak doğrulayamazsanız yayma özelliğini kullanın. -
Deşarj: Üyenizin imzasını güvende bırakın. Üyenin içindeki yükümlülüğü doğrulayın, genellikle çalışma zamanı korumalarıyla, ardından güvenli olmayan işlemi bir iç
unsafeblokta gerçekleştirin. İçunsafeblok içeren ancak kendi imzasınıunsafeişaretlemeyen bir üye güvenli olmayan bir sınırdır: güvenli olmayan kodu güvenli çağrılabilen bir yüzeye dönüştürür.
Aşağıdaki önizleme mockup, girdisini bir koruma ile doğrular, yönetilen bir diziyi sabitler ve işaretçiyi okur. Çağıranların bağlama unsafe ihtiyacı yoktur, çünkü yöntem yükümlülüğü devre dışı bırakılır:
// 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 denetimi ve dizi uzunluğu, bir okumanın arabelleğinden geçmesine izin verecek girişleri eler, bu nedenle bloğun unsafe içindeki başvurunun ses olması gerekir. yöntemi artık yükümlülük bırakmaz, bu nedenle güvenli çağrılabilen bir imza ortaya çıkarır.
Güvenlik belgeleri
Arayan-güvensiz üye, arayanın garanti etmesi gerekenleri belgelemelidir. Güncelleştirilmiş model iki tamamlayıcı açıklama stilini teşvik eder:
-
/// <safety>İmzanın üzerindeki belge bloğu resmi sözleşmeyi belirtir: Çağıranın karşılaması gereken koşullar. Çözümleyici, eksik bir arayan-güvensiz üyeye bayrak ekleyebilir. - Blok
// SAFETY:içindeki açıklamaunsafe, gövdeyi okuyan geliştiriciler ve denetçiler için işlemin neden o noktada ses çıkardığını kaydeder.
Aşağıdaki önizleme mockup, arayan-güvenli ReadByte olmayan bir yöntemde her iki stili de gösterir:
// 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];
}
}
Blok /// <safety> size sözleşmeyi söyler. Sözleşme, her arayan ve gözden geçirenin gördüğü belgelere aittir.
Güvenli olmayan alanlar
Bir alan için değiştiriciyi unsafe , bildirilen türü kapsayan türün koruduğu ve diğer kodun bağımlı olduğu sözleşmeleri ifade etmediğinde kullanın. Güvensizlik, tür sisteminin gördükleriyle türün vaatleri arasındaki boşlukta bulunur. Değiştirici, alana her yazma işlemini bir bloğun içine zorlar ve bu da yazmaların tek bir unsafe yerde gözden geçirilebilir olmasını sağlar.
En net durum, yerel işaretçiyi tutan bir alandır. İşaretçi, kaç bayt olarak adreslediğini System.Span<T> bildirmez, bu nedenle içeren tür bu bilgilerin kendisini korur:
// 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];
}
}
}
Alan readonly unsafe , sözleşmeyi yerleşik bir korumayla eşleştirdi: unsafe sabiti adlandırın ve readonly inşaat sonrasında bozabilecek bir yazma işlemini önler. Bir özelliğin veya olayın unsafe işaretlenmesi, arka alan çağıranı güvenli yapmaz. ile [StructLayout(LayoutKind.Explicit)]bir yapıda, her alanı safe veya unsafeolarak işaretlersiniz.
Safe anahtar sözcüğü
Güncelleştirilmiş model, safe derleyicinin seçimi açık yapmanızı gerektirdiği bir bildirimin sağlam olduğunu kanıtlayan bağlamsal bir anahtar sözcük ekler.
Bir extern üye yerel kodu çağırır, böylece derleyici güvenliğini sınıflandıramaz. Güncelleştirilmiş modelin altında, kısmi bir LibraryImport yöntem safeunsafede dahil olmak üzere veya olmak üzere her extern bildirimi işaretlersiniz:
// Preview
[LibraryImport("libc")]
internal static safe partial int getpid();
[LibraryImport("libc", StringMarshalling = StringMarshalling.Utf8)]
internal static unsafe partial nint strlen(byte* str);
getpid parametre almaz ve ilkel bir değer döndürür, bu nedenle yazar çağrının güvenli olduğunu ve çağıranların bunu seremoni olmadan kullandığını doğrular.
strlen yerel kodun başvuruyu kaldırdığını belirten ham bir işaretçi alır, bu nedenle bildirimdir unsafe ve çağıranlara yükümlülüğü yayır. Her iki değiştiricinin de atlanması bir hatadır ve bu da sizi güvenlik kararına zorlar. Açık düzene sahip bir yapıdaki alan aynı kuralı kullanır.
Kabul etme ve çapraz derleme davranışı
Güncelleştirilmiş modelde iki bağımsız proje düzeyi anahtarı vardır:
- Yeni bir kabul etme özelliği güncelleştirilmiş kuralları açar. Özellik kapalı olduğunda, özgün kurallar uygulanır. Açık olduğunda,
unsafebir üyede çağıranlara yayılır ve derleyici, derlemedeki seçimi özniteliğiyle MemorySafetyRulesAttribute kaydeder. - Mevcut AllowUnsafeBlocks özelliği, çağrı sitelerindeki iç bloklar da dahil olmak üzere anahtar sözcüğün her görünümünü
unsafekapsıyor. Varsayılan olarak olarak kullanılırfalse, bu nedenle varsayılan değerdeki bir proje güvenli olmayan API'leri çağıramaz.
İki özellik aşağıdaki gibi birleştirilir:
| Kabul etme özelliği | AllowUnsafeBlocks |
Result |
|---|---|---|
| Açık | Kapalı (varsayılan) | En güvenli yapılandırma. Proje güncelleştirilmiş modeli kullanır ve güvenli olmayan koda izin vermiyor. |
| Açık | Açık | Proje güncelleştirilmiş modeli kullanır ve güvenli olmayan koda izin verir. |
| Off | Off | Özgün model uygulanır ve proje işaretçi türlerini kullanamaz. |
| Off | Açık | Özgün model uygulanır ve proje işaretçi türlerini kullanabilir. |
Bir derlemenin güncelleştirilmiş kuralları başka bir derlemeye karşı zorlayıp zorlamadığı, hangi tarafın kabul ettiğine bağlıdır:
-
Güncelleştirilmiş model çağıranı, güncelleştirilmiş model çağıranı: Çağıranın
unsafeişaretçileri meta veriler arasında hareket eder. Çağıran, her çağrıyı bir bloktaki arayan-güvensiz üyeyeunsafesarmalar. -
Güncelleştirilmiş model çağıranı, özgün model arayan: Uyumluluk modu, çağıran üyeye imzasında bir işaretçi türüyle çağıran-güvenli değil olarak davranır, bu nedenle çağrı sitesinin kapsayan
unsafebir bloka ihtiyacı vardır. Bu mod, işaretçi tabanlı bir API'nin gereksinimini sessizce kaybetmesiniunsafeengeller. - Özgün model çağıranı, güncelleştirilmiş model çağıranı: Özgün işaretçi kuralları hala geçerlidir. özgün model çağıranı yeni işaretçileri okuyamadığından, imzasında işaretçi türü olmayan bir çağıran güvenli olmayan üye güvenli koddan çağrılabilir hale gelir.
C# dil belirtimi
Daha fazla bilgi için C# dil belirtimininGüvenli olmayan kod bölümüne bakın.
Güncelleştirilmiş bellek güvenliği modelinin tasarımı için bkz. bellek güvenliği özelliği belirtimi.