İngilizce dilinde oku

Aracılığıyla paylaş


Güvenli olmayan kod, işaretçi türleri ve işlev işaretçileri

Yazdığınız C# kodunun çoğu "doğrulanabilir güvenli kod" doğrulanabilir güvenli kod .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# doğrulanamaz kod yazabileceğiniz bir unsafe bağlamını destekler. unsafe bağlamında kod, işaretçileri kullanabilir, bellek blokları ayırıp serbest bırakabilir ve işlev işaretçilerini kullanarak yöntemleri çağırabilir. C# dilinde güvenli olmayan kod her zaman tehlikeli değildir; güvenliğinin doğrulanamadığı bir koddur.

Güvenli olmayan kod aşağıdaki özelliklere sahiptir:

  • Yöntemler, türler ve kod blokları güvenli değil olarak tanımlanabilir.
  • Bazı durumlarda, güvenli olmayan kod dizi sınırları denetimlerini kaldırarak uygulamanın performansını artırabilir.
  • İşaretçi gerektiren yerel işlevleri çağırdığınızda güvenli olmayan kod gereklidir.
  • Güvenli olmayan kodun kullanılması güvenlik ve kararlılık risklerine neden olur.
  • Güvenli olmayan bloklar içeren kod, AllowUnsafeBlocks derleyici seçeneğiyle derlenmelidir.

İş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:

C#
type* identifier;
void* identifier; //allowed but not recommended

İşaretçi türündeki *'dan önce belirtilen tür, atıfta bulunan tür olarak adlandırılır.

İşaretçi türleri nesne devralmaz ve işaretçi türleri ile objectarasında 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ılır. İşaretçi adlarının hiçbirine ön ek olarak kullanılmaz. Mesela:

C#
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 nesne sabitlenmelidir.

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: p bir 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: p bir karakter işaretçisidir.
  • void* p: p bilinmeyen bir türe yönelik bir işaretçidir.

İşaretçi dolaylı işleci * işaretçi değişkeninin işaret ettiği konumdaki içeriklere erişmek için kullanılabilir. Örneğin, aşağıdaki bildirimi göz önünde bulundurun:

C#
int* myVariable;

*myVariable ifadesi, myVariableiçinde bulunan adreste bulunan int 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.

C#
// 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. Herhangi bir işaretçi türü, bir atama ifadesi kullanılarak açıkça başka bir işaretçi türüne dönüştürülebilir. 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.

C#
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
    */
}

Sabit boyutlu arabellekler

fixed anahtar sözcüğünü kullanarak veri yapısında sabit boyutlu bir dizi içeren bir arabellek oluşturabilirsiniz. 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 bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, floatveya doubleolmasıdır.

C#
private fixed char name[30];

Güvenli kodda, dizi içeren bir C# yapısı dizi öğelerini içermez. Yapısı bunun yerine öğelere bir başvuru içerir. yapı sabit boyutlu bir diziyi, güvenli olmayan bir kod bloğunda kullanıldığında ekleyebilirsiniz.

Aşağıdaki struct boyutu dizideki öğelerin sayısına bağlı değildir çünkü pathName bir başvurudur:

C#
public struct PathArray
{
    public char[] pathName;
    private int reserved;
}

Yapı, güvenli olmayan kodda eklenmiş bir dizi içerebilir. Aşağıdaki örnekte, fixedBuffer dizisi sabit bir boyuta sahiptir. İlk öğeye bir işaretçi almak için fixed deyimi kullanırsınız. Bu işaretçi aracılığıyla dizinin öğelerine erişirsiniz. fixed deyimi, fixedBuffer örneği alanını bellekteki belirli bir konuma sabitler.

C#
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. Önceki örnekte, bir unsafe structiçinde sabit boyutlu bir arabelleğin nasıl var olabileceği gösterilmektedir.

C#
internal unsafe struct Buffer
{
    public fixed char fixedBuffer[128];
}

Buffer için derleyici tarafından oluşturulan C# aşağıdaki gibi özniteliklendirilir:

C#
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:

  • Yalnızca unsafe bağlamında kullanılabilir.
  • Yalnızca yapıların örnek alanları olabilir.
  • Bunlar her zaman vektör veya tek boyutlu dizilerdir.
  • Bildirimi, fixed char id[8]gibi uzunluğu içermelidir. fixed char id[]kullanamazsınız.

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, Copy yönteminde işaretçileri kullanmanıza olanak tanıyan güvenli olmayan anahtar sözcüğü kullanılır. sabit deyimi, kaynak ve hedef dizilere işaretçileri bildirmek için kullanılır. fixed deyimi çöp toplamanın dizileri taşımaması için bellekteki kaynak ve hedef dizilerin konumunu sabitler. fixed bloğu tamamlandığında dizilerin bellek bloklarının sabitlenmesi kaldırılır. Bu örnekteki Copy yöntemi unsafe anahtar sözcüğünü kullandığından, AllowUnsafeBlocks derleyici seçeneğiyle derlenmelidir.

Bu örnek, ikinci bir yönetilmeyen işaretçi yerine dizinleri kullanarak her iki dizinin öğelerine de erişir. pSource ve pTarget işaretçilerinin bildirimi dizileri sabitler.

C#
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
    */
}

İş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.

delegate* söz dizimini kullanarak bir işlev işaretçisi tanımlayabilirsiniz. Derleyici, bir delegate nesnesinin örneğini oluşturmak ve Invokeçağırmak yerine calli yönergesini kullanarak işlevini çağırır. 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:

C#
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 bildireceğini ve bu yerel işlevin işaretçisini kullanarak UnsafeCombine yöntemini nasıl çağırabileceğinizi gösterir:

C#
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çileri yalnızca unsafe bağlamında bildirilebilir.
  • delegate* alan (veya delegate*döndüren) yöntemler yalnızca unsafe bağlamında çağrılabilir.
  • bir işlevin adresini almak için & işlecine yalnızca static iş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.

managed ve unmanagedanahtar sözcüklerini kullanarak bir delegate* için ç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.

C#
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);

İşlev işaretçisi özellik belirtiminde işlev işaretçileri hakkında daha fazla bilgi edinebilirsiniz.

C# dil belirtimi

Daha fazla bilgi için C# dil belirtimininGüvenli olmayan kod bölümüne bakın.