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 koddur." 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#, içinde doğrulamaz kod yazabileceğiniz bir unsafe bağlamı destekler. Bir unsafe bağlamda, kod işaretçileri kullanabilir, bellek blokları ayırabilir ve boş kullanabilir ve işlev işaretçilerini kullanarak yöntemleri çağırabilir. C# dilinde güvenli olmayan kod her zaman tehlikeli değildir; yalnızca güvenliği doğrulanamayan kod.

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. Bir işaretçi türü bildirimi, aşağıdaki biçimlerden birini alır:

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

İşaretçi türünden * önce belirtilen tür, başvuran türü olarak adlandırılır. Yalnızca yönetilmeyen bir tür bir başvuran türü olabilir.

İş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 ve işaretçi türleri ve tamsayı türleri arasında dönüştürme yapabilirsiniz.

Aynı bildirimde birden çok işaretçi bildirdiğinizde, yıldız işareti (*) yalnızca temel türle birlikte yazılır. Her işaretçi adına ön ek olarak kullanılmaz. Örneğin:

int* p1, p2, p3;   // Ok
int *p1, *p2, *p3;   // Invalid in C#

bir işaretçi başvuruya veya başvuru içeren bir yapıya işaret edilemez, çünkü bir işaretçi buna işaret ediyor olsa bile nesne başvurusu çöp olarak toplanabilir. Çöp toplayıcı, herhangi bir işaretçi türü tarafından bir nesnenin işaret edilip edilmediğini izlemez.

türündeki işaretçi değişkeninin değeri, türünde MyType*MyTypebir değişkenin adresidir. Aşağıda, işaretçi türü bildirimi örnekleri verilmiştir:

  • int* p: p bir tamsayının işaretçisidir.
  • int** p: p bir tamsayı işaretçisine işaret eden bir işaretçidir.
  • int*[] p: p tamsayılara yönelik işaretçilerin tek boyutlu bir dizisidir.
  • char* p: p bir karakterin işaretçisidir.
  • void* p: p bilinmeyen bir türün işaretçisidir.

İş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 ele alalım:

int* myVariable;

İfade*myVariable, içinde bulunan myVariableadreste bulunan değişkeni belirtirint.

Deyimindeki makalelerde fixed birkaç işaretçi örneği vardır. Aşağıdaki örnek anahtar sözcüğünü unsafe ve deyimini kullanır ve bir iç işaretçinin fixed nasıl artırılıp artırılamını gösterir. Bu kodu çalıştırmak için bir konsolun Ana işlevine yapıştırabilirsiniz. Bu örneklerin AllowUnsafeBlocks derleyici seçenek kümesiyle derlenmiş olması gerekir.

// 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
*/

dolaylı işlecini türündeki void*bir işaretçiye uygulayamazsınız. Ancak, boş bir işaretçiyi başka herhangi bir türü dönüştürmek veya bunun tersini yapmak için bir yayın kullanabilirsiniz.

İşaretçi olabilir null. Yönlendirme işlecini bir null işaretçiye uygulamak, uygulama tarafından tanımlanan bir davranışa neden olur.

İşaretçilerin yöntemler arasında geçirilmesi tanımsız davranışa neden olabilir. bir , outveya ref parametresi aracılığıyla veya işlev sonucu olarak yerel bir değişkene işaretçi döndüren bir inyöntem düşünün. İşaretçi sabit bir blokta ayarlandıysa, işaret ettiği değişken artık sabit olamaz.

Aşağıdaki tabloda, güvenli olmayan bir bağlamda işaretçiler üzerinde işlem yapabilecek işleçler ve deyimler listelenmektedir:

İşleç/Deyim Kullanma
* İşaretçi yöneltmesi gerçekleştirir.
-> Bir yapının bir üyesine bir işaretçi yoluyla erişir.
[] Bir iş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ığında bellek ayırır.
fixed Deyim Adresinin bulunamaması için bir değişkeni geçici olarak sabitler.

İş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üre dönüştürülebilir. Herhangi bir işaretçi türüne değeri nullatanabilir. 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. Ayrıca, herhangi bir integral türünü işaretçi türüne veya herhangi bir işaretçi türünü tam sayı türüne dönüştürebilirsiniz. Bu dönüştürmeler açık bir atama gerektirir.

Aşağıdaki örnek, öğesini int* öğesine 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( 4 bayt) boyutuna int kadar 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: {0}", number);

    /* Output:
        The 4 bytes of the integer: 00 04 00 00
        The value of the integer: 1024
    */
}

Sabit boyutlu arabellekler

Veri yapısında fixed sabit boyutlu bir dizi ile arabellek oluşturmak için anahtar sözcüğünü kullanabilirsiniz. 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, , longsbyte, uintfloatushortulong, veya doubleolmasıdır.boolcharbyte

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. Güvenli olmayan bir kod bloğunda kullanıldığında yapıya sabit boyutlu bir dizi ekleyebilirsiniz.

Aşağıdakilerin struct boyutu, bir başvuru olduğundan pathName dizideki öğelerin sayısına bağlı değildir:

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

Yapı, güvenli olmayan kodda eklenmiş bir dizi içerebilir. Aşağıdaki örnekte dizinin fixedBuffer boyutu sabittir. İlk öğeye bir işaretçi almak için deyimini kullanırsınızfixed. Bu işaretçi aracılığıyla dizinin öğelerine erişirsiniz. deyimi, fixed örnek 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]);
    }
}

128 öğe char dizisinin 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, karakter arabellekleri veya CharSet = CharSet.Ansiile CharSet = CharSet.Auto API yöntemlerine veya yapılarına göre sıralanmış olsa bile aynıdır. Daha fazla bilgi için bkz. CharSet.

Yukarıdaki örnekte, sabitleme olmadan alanlara erişim fixed gösterilmektedir. Bir diğer yaygın sabit boyutlu dizi de bool dizisidir. Bir bool dizideki öğelerin boyutu her zaman 1 bayttır. bool diziler, bit dizileri veya arabellekler oluşturmak için uygun değildir.

Sabit boyutlu arabellekler, ortak dil çalışma zamanına System.Runtime.CompilerServices.UnsafeValueTypeAttribute(CLR) bir türün taşma olasılığı olan yönetilmeyen bir dizi içerdiğini belirten ile derlenir. Stackalloc kullanılarak ayrılan bellek, CLR'de arabellek taşması algılama özelliklerini de otomatik olarak etkinleştirir. Önceki örnekte, içinde sabit boyutlu bir arabelleğin nasıl bulunabileceği unsafe structgösterilmektedir.

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

için Buffer 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:

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

Bayt dizisine 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 işaretçileri Copy 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. deyimi, fixed kaynak ve hedef dizilerin konumunu bellekte sabitler, böylece bunlar çöp toplama tarafından taşınmaz. Blok tamamlandığında diziler için bellek blokları kaldırılır fixed . Bu örnekteki Copy yöntem anahtar sözcüğünü kullandığından unsafe 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. ve pTarget işaretçilerinin pSource 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
    */
}

İşlev işaretçileri

C# güvenli işlev işaretçisi nesnelerini tanımlamak için türler sağlar delegate . Bir temsilciyi çağırmak, türünden System.Delegate türetilen bir türün örneğini oluşturmayı ve yöntemine Invoke sanal bir yöntem çağrısı yapmayı içerir. Bu sanal çağrı IL yönergesini callvirt kullanır. Performans açısından kritik kod yollarında IL yönergesinin calli kullanılması daha verimlidir.

Söz dizimini kullanarak bir işlev işaretçisi delegate* tanımlayabilirsiniz. Derleyici, bir delegate nesne örneği oluşturup çağırmak yerine yönergesini calli kullanarak işlevini çağırırInvoke. Aşağıdaki kod, aynı türdeki iki nesneyi birleştirmek için veya delegatedelegate* 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* bildirim kullanır:

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

Aşağıdaki kod, statik bir yerel işlevi nasıl bildireceğini ve bu yerel işlevin UnsafeCombine işaretçisini kullanarak yöntemini nasıl çağırabileceğinizi gösterir:

static int localMultiply(int x, int y) => x * y;
int 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 bir unsafe bağlamda bildirilebilir.
  • Alan (veya döndürendelegate*) yöntemler delegate* yalnızca bir unsafe bağlamda çağrılabilir.
  • bir & işlevin adresini almak için işlecine yalnızca işlevlerde static izin verilir. (Bu kural hem üye işlevleri hem de yerel işlevler için geçerlidir).

Söz diziminin türleri bildirme delegate ve işaretçileri kullanma ile paralelleri vardır. üzerindeki *delegate sonek, bildirimin bir işlev işaretçisi olduğunu gösterir. & bir işlev işaretçisine yöntem grubu atarken, işlemin yöntemin adresini aldığını gösterir.

ve unmanagedanahtar sözcüklerini managed kullanarak için delegate* çağırma kuralını belirtebilirsiniz. Ayrıca, işlev işaretçileri için unmanaged çağırma kuralını belirtebilirsiniz. Aşağıdaki bildirimler her birinin örneklerini gösterir. İlk bildirim, varsayılan olan çağırma kuralını kullanır managed . Sonraki dörtte bir unmanaged çağırma kuralı kullanılır. Her biri ECMA 335 çağrı kurallarından birini belirtir: Cdecl, Stdcall, Fastcallveya Thiscall. Son bildirim, CLR'ye platform için varsayılan çağırma kuralını seçmesini bildirerek çağırma kuralını kullanır unmanaged . CLR, çalışma zamanında çağırma kuralını seçer.

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

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

C# dili belirtimi

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