Bagikan melalui


23 Kode tidak aman

23.1 Umum

Implementasi yang tidak mendukung kode yang tidak aman diperlukan untuk mendiagnosis penggunaan aturan sintaktik yang ditentukan dalam klausa ini.

Sisa klausul ini, termasuk semua subklasulnya, bersifat normatif secara kondisional.

Catatan: Bahasa C# inti, seperti yang didefinisikan dalam klausul sebelumnya, berbeda secara signifikan dari C dan C++ karena tidak adanya pointer sebagai jenis data. Sebagai gantinya, C# menyediakan referensi dan kemampuan untuk membuat objek yang dikelola oleh pengumpul sampah. Desain ini, ditambah dengan fitur lain, menjadikan C# bahasa yang jauh lebih aman daripada C atau C++. Dalam bahasa inti C#, tidak mungkin memiliki variabel yang belum diinisialisasi, penunjuk yang menggantung, atau ekspresi yang mengakses array di luar batasnya. Seluruh kategori bug yang secara rutin mengganggu program C dan C++ dengan demikian dihilangkan.

Meskipun praktis setiap konstruksi jenis pointer di C atau C++ memiliki rekan jenis referensi di C#, meskipun demikian, ada situasi di mana akses ke jenis pointer menjadi kebutuhan. Misalnya, berinteraksi dengan sistem operasi yang mendasarinya, mengakses perangkat yang dipetakan memori, atau menerapkan algoritma kritis waktu mungkin tidak mungkin atau praktis tanpa akses ke penunjuk. Untuk mengatasi kebutuhan ini, C# menyediakan kemampuan untuk menulis kode yang tidak aman.

Dalam kode yang tidak aman, dimungkinkan untuk mendeklarasikan dan beroperasi pada pointer, untuk melakukan konversi antara pointer dan jenis integral, untuk mengambil alamat variabel, dan sebagainya. Dalam arti tertentu, menulis kode yang tidak aman sama seperti menulis kode C dalam program C#.

Kode yang tidak aman sebenarnya adalah fitur "aman" dari perspektif pengembang dan pengguna. Kode yang tidak aman harus ditandai dengan jelas dengan pengubah unsafe, sehingga pengembang tidak mungkin menggunakan fitur yang tidak aman secara tidak sengaja, dan mesin eksekusi berfungsi untuk memastikan bahwa kode yang tidak aman tidak dapat dijalankan di lingkungan yang tidak tepercaya.

catatan akhir

23.2 Konteks tidak aman

Fitur C# yang tidak aman hanya tersedia dalam konteks yang tidak aman. Konteks yang tidak aman diperkenalkan dengan menyertakan pengubah unsafe dalam deklarasi jenis, anggota, atau fungsi lokal, atau dengan menggunakan unsafe_statement:

  • Deklarasi kelas, struktur, antarmuka, atau delegasi dapat mencakup pengubah unsafe , dalam hal ini, seluruh tingkat tekstual dari deklarasi jenis tersebut (termasuk isi kelas, struktur, atau antarmuka) dianggap sebagai konteks yang tidak aman.

    Catatan: Jika type_declaration parsial, hanya bagian tersebut yang merupakan konteks yang tidak aman. catatan akhir

  • Deklarasi bidang, metode, properti, peristiwa, pengindeks, operator, konstruktor instans, finalizer, konstruktor statis, atau fungsi lokal dapat mencakup unsafe pengubah, dalam hal ini, seluruh tingkat tekstual deklarasi anggota tersebut dianggap sebagai konteks yang tidak aman.
  • unsafe_statement memungkinkan penggunaan konteks yang tidak aman dalam blok. Seluruh rentang teks dari blok terkait dianggap sebagai konteks yang tidak aman untuk digunakan. Fungsi lokal yang dinyatakan dalam konteks tidak aman itu sendiri tidak aman.

Ekstensi tata bahasa terkait ditunjukkan di bawah ini dan pada subklaus berikutnya.

unsafe_modifier
    : 'unsafe'
    ;

unsafe_statement
    : 'unsafe' block
    ;

Contoh: Dalam kode berikut

public unsafe struct Node
{
    public int Value;
    public Node* Left;
    public Node* Right;
}

pengubah unsafe yang ditentukan dalam deklarasi struktur menyebabkan seluruh tingkat tekstual deklarasi struktur menjadi konteks yang tidak aman. Dengan demikian, dimungkinkan untuk mendeklarasikan Left dan Right bidang menjadi jenis penunjuk. Contoh di atas juga dapat ditulis

public struct Node
{
    public int Value;
    public unsafe Node* Left;
    public unsafe Node* Right;
}

Di sini, pengubah unsafe dalam deklarasi lapangan menyebabkan deklarasi tersebut dianggap sebagai konteks yang tidak aman.

contoh akhir

Selain menetapkan konteks yang tidak aman, sehingga mengizinkan penggunaan jenis pointer, unsafe modifier tidak mempengaruhi tipe atau anggotanya.

Contoh: Dalam kode berikut

public class A
{
    public unsafe virtual void F() 
    {
        char* p;
        ...
    }
}

public class B : A
{
    public override void F() 
    {
        base.F();
        ...
    }
}

modifier tidak aman pada metode F di A hanya menyebabkan luas tekstual F menjadi konteks tidak aman di mana fitur tidak aman dari bahasa dapat digunakan. Dalam penimpaan F dalam B, tidak perlu mengulangi penentuan pengubah unsafe—kecuali, tentu saja, metode F dalam B itu sendiri membutuhkan akses ke fitur yang tidak aman.

Situasinya sedikit berbeda ketika jenis pointer adalah bagian dari tanda tangan metode

public unsafe class A
{
    public virtual void F(char* p) {...}
}

public class B: A
{
    public unsafe override void F(char* p) {...}
}

Di sini, karena F signature menyertakan pointer, signature hanya dapat ditulis dalam konteks yang tidak aman. Namun, konteks yang tidak aman dapat diperkenalkan dengan membuat seluruh kelas tidak aman, seperti halnya dalam A, atau dengan menyertakan pengubah unsafe dalam deklarasi metode, seperti halnya dalam B.

contoh akhir

Ketika pengubah unsafe digunakan pada deklarasi jenis parsial (§15.2.7), hanya bagian tertentu yang dianggap sebagai konteks yang tidak aman.

23.3 Jenis penunjuk

Dalam konteks tidak aman, type (§8.1) dapat berupa pointer_type serta value_type, reference_type, atau type_parameter. Dalam konteks yang tidak aman, pointer_type mungkin juga merupakan jenis elemen array (§17). pointer_type juga dapat digunakan dalam ekspresi typeof (§12.8.18) di luar konteks yang aman (karena penggunaan tersebut tidak berbahaya).

Pointer_type ditulis sebagai unmanaged_type (§8,8) atau kata kunci void, diikuti dengan * token:

pointer_type
    : value_type ('*')+
    | 'void' ('*')+
    ;

Jenis yang ditentukan sebelum * dalam jenis penunjuk disebut tipe referensi dari jenis penunjuk. Ini mewakili jenis variabel di mana nilai dari tipe pointer menunjuk.

pointer_type hanya dapat digunakan dalam array_type dalam konteks yang tidak aman (§23.2). Non_array_type adalah jenis apa pun yang bukan jenis array_type.

Tidak seperti referensi (nilai dari tipe referensi), pointer tidak dilacak oleh pengumpul sampah—pengumpul sampah tidak memiliki pengetahuan tentang pointer dan data yang mereka arahkan. Untuk alasan ini, pointer tidak diizinkan untuk menunjuk ke referensi atau ke struct yang berisi referensi, dan jenis referensi pointer harus menjadi unmanaged_type. Jenis penunjuk itu sendiri adalah jenis yang tidak dikelola, sehingga jenis penunjuk dapat digunakan sebagai jenis referensi untuk jenis pointer lain.

Aturan intuitif untuk pencampuran pointer dan referensi adalah bahwa objek dari referensi diperbolehkan mengandung pointer, tetapi objek dari pointer tidak diperbolehkan mengandung referensi.

Contoh: Beberapa contoh jenis pointer diberikan dalam tabel di bawah ini:

Contoh Keterangan
byte* Penunjuk ke byte
char* Penunjuk ke char
int** Penunjuk ke penunjuk ke int
int*[] Array penunjuk dimensi tunggal ke int
void* Penunjuk ke tipe yang tidak diketahui

contoh akhir

Untuk implementasi tertentu, semua jenis pointer harus memiliki ukuran dan representasi yang sama.

Catatan: Tidak seperti C dan C++, ketika beberapa pointer dideklarasikan dalam deklarasi yang sama, di C# * ditulis bersama dengan jenis yang mendasarinya saja, bukan sebagai tanda baca awalan pada setiap nama penunjuk. Contohnya:

int* pi, pj; // NOT as int *pi, *pj;  

catatan akhir

Nilai pointer yang memiliki jenis T* mewakili alamat variabel jenis T. Operator * tidak langsung pointer (§23.6.2) dapat digunakan untuk mengakses variabel ini.

Contoh: Diberikan variabel P bertipe int*, ekspresi *P menunjukkan variabel int yang ditemukan di alamat yang terdapat dalam P. contoh akhir

Seperti referensi objek, pointer mungkin null. Menerapkan operator pengindeksan ke sebuah penunjuk dengan nilai null menghasilkan perilaku berdasarkan implementasi (§23.6.2). Pointer dengan nilai null diwakili oleh all-bits-zero.

Tipe void* mewakili penunjuk ke tipe yang tidak diketahui. Karena jenis referensi tidak diketahui, operator tidak langsung tidak dapat diterapkan ke penunjuk jenis void*, juga tidak dapat melakukan aritmetika apa pun pada pointer tersebut. Namun, penunjuk jenis void* dapat ditransmisikan ke jenis pointer lain (dan sebaliknya) dan dibandingkan dengan nilai jenis pointer lainnya (§23.6.8).

Jenis penunjuk adalah kategori jenis terpisah. Tidak seperti jenis referensi dan jenis nilai, jenis pointer tidak mewarisi dari object dan tidak ada konversi antara jenis pointer dan object. Secara khusus, boxing dan unboxing (§8.3.13) tidak didukung untuk pointer. Namun, konversi diizinkan antara jenis pointer yang berbeda dan antara jenis pointer dan jenis integral. Ini dijelaskan dalam §23.5.

Pointer_type tidak dapat digunakan sebagai argumen tipe (§8.4), dan inferensi jenis (§12.6.3) gagal pada panggilan metode generik yang akan menyimpulkan argumen tipe menjadi jenis penunjuk.

pointer_type tidak dapat digunakan sebagai jenis subekspresi operasi yang terikat secara dinamis (§12.3.3).

pointer_type tidak dapat digunakan sebagai jenis parameter pertama dalam metode ekstensi (§15.6.10).

pointer_type dapat digunakan sebagai jenis bidang volatil (§15.5.4).

Penghapusan dinamis jenis E* adalah jenis penunjuk dengan jenis referensi dari penghapusan Edinamis .

Ekspresi dengan tipe penunjuk tidak dapat digunakan untuk memberikan nilai dalam deklarator_anggota dalam ekspresi_pembuatan_objek_anonim (§12.8.17.3).

Nilai default (§9,3) untuk jenis penunjuk apa pun adalah null.

Catatan: Meskipun pointer dapat diteruskan sebagai parameter berdasarkan referensi, melakukannya dapat menyebabkan perilaku yang tidak terdefinisi, karena pointer mungkin diatur untuk menunjuk ke variabel lokal yang tidak lagi ada ketika metode yang disebut kembali, atau objek tetap yang dulu ditunjuk, tidak lagi tetap. Contohnya:

class Test
{
    static int value = 20;

    unsafe static void F(out int* pi1, ref int* pi2) 
    {
        int i = 10;
        pi1 = &i;       // return address of local variable
        fixed (int* pj = &value)
        {
            // ...
            pi2 = pj;   // return address that will soon not be fixed
        }
    }

    static void Main()
    {
        int i = 15;
        unsafe 
        {
            int* px1;
            int* px2 = &i;
            F(out px1, ref px2);
            int v1 = *px1; // undefined
            int v2 = *px2; // undefined
        }
    }
}

catatan akhir

Metode dapat mengembalikan nilai dari beberapa jenis, dan jenis tersebut dapat menjadi penunjuk.

Contoh: Ketika diberikan penunjuk ke urutan yang berdekatan int, jumlah elemen urutan tersebut, dan beberapa nilai lainnya int , metode berikut mengembalikan alamat nilai tersebut dalam urutan tersebut, jika terjadi kecocokan; jika tidak, maka akan mengembalikan null:

unsafe static int* Find(int* pi, int size, int value)
{
    for (int i = 0; i < size; ++i)
    {
        if (*pi == value)
        {
            return pi;
        }
        ++pi;
    }
    return null;
}

contoh akhir

Dalam konteks yang tidak aman, beberapa konstruksi tersedia untuk beroperasi pada pointer:

  • Operator unary * dapat digunakan untuk melakukan tidak langsung pointer (§23.6.2).
  • Operator -> dapat digunakan untuk mengakses anggota struct melalui pointer (§23.6.3).
  • Operator [] dapat digunakan untuk mengindeks pointer (§23.6.4).
  • Operator unary & dapat digunakan untuk mendapatkan alamat variabel (§23.6.5).
  • Operator ++ dan -- dapat digunakan untuk kenaikan dan penunjuk penurunan (§23.6.6).
  • Biner + dan - operator dapat digunakan untuk melakukan aritmatika pointer (§23.6.7).
  • Operator ==, , !=<, >, <=, dan >= dapat digunakan untuk membandingkan pointer (§23.6.8).
  • Operator stackalloc dapat digunakan untuk mengalokasikan memori dari tumpukan panggilan (§23,9).
  • Pernyataan fixed dapat digunakan untuk memperbaiki variabel sementara sehingga alamatnya dapat diperoleh (§23,7).

23.4 Variabel tetap dan dapat dipindahkan

Alamat operator (§23.6.5) dan fixed pernyataan (§23.7) membagi variabel menjadi dua kategori: Variabel tetap dan variabel yang dapat dipindahkan.

Variabel tetap berada di lokasi penyimpanan yang tidak terpengaruh oleh pengoperasian pengumpul sampah. (Contoh variabel tetap termasuk variabel lokal, parameter nilai, dan variabel yang dibuat oleh dereferensi pointer.) Di sisi lain, variabel yang dapat dipindahkan berada di lokasi penyimpanan yang tunduk pada relokasi atau pembuangan oleh pengumpul sampah. (Contoh variabel yang dapat dipindahkan mencakup bidang dalam objek dan elemen array.)

Operator & (§23.6.5) mengizinkan alamat variabel tetap diperoleh tanpa batasan. Namun, karena variabel yang dapat dipindahkan tunduk pada relokasi atau pembuangan oleh pengumpul sampah, alamat variabel yang dapat dipindahkan hanya dapat diperoleh menggunakan fixed statement (§23,7), dan alamat tersebut tetap valid hanya selama durasi fixed pernyataan itu.

Secara tepat, variabel tetap adalah salah satu hal berikut:

  • Variabel yang dihasilkan dari simple_name (§12.8.4) yang mengacu pada variabel lokal, parameter nilai, atau array parameter, kecuali variabel tersebut ditangkap oleh fungsi anonim (§12.19.6.2).
  • Variabel yang dihasilkan dari member_access (§12.8.7) dengan bentuk V.I, di mana V adalah variabel tetap dari struct_type.
  • Variabel yang dihasilkan dari pointer_indirection_expression (§23.6.2) dari formulir *P, pointer_member_access (§23.6.3) dari formulir P->I, atau pointer_element_access (§23.6.4) dari formulir P[E].

Semua variabel lain diklasifikasikan sebagai variabel yang dapat dipindahkan.

Bidang statis diklasifikasikan sebagai variabel yang dapat dipindahkan. Selain itu, parameter berdasarkan referensi diklasifikasikan sebagai variabel yang dapat dipindahkan, bahkan jika argumen yang diberikan untuk parameter adalah variabel tetap. Akhirnya, variabel yang dihasilkan dengan mendereferensikan pointer selalu diklasifikasikan sebagai variabel tetap.

23.5 Konversi penunjuk

23.5.1 Umum

Dalam konteks tidak aman, kumpulan konversi implisit yang tersedia (§10.2) diperluas untuk menyertakan konversi pointer implisit berikut:

  • Dari pointer_type mana pun ke tipe void*.
  • Dari null literal (§6.4.5.7) ke apa pun pointer_type.

Selain itu, dalam konteks tidak aman, kumpulan konversi eksplisit yang tersedia (§10,3) diperluas untuk menyertakan konversi pointer eksplisit berikut:

  • Dari pointer_type ke pointer_type lainnya.
  • Dari sbyte, byte, short, ushort, int, uint, long, atau ulong ke jenis pointer apa pun.
  • Dari pointer_type apa pun ke sbyte, byte, short, ushort, int, uint, long, atau ulong.

Terakhir, dalam konteks tidak aman, kumpulan konversi implisit standar (§10.4.2) mencakup konversi pointer berikut:

  • Dari pointer_type mana pun ke tipe void*.
  • Dari literal null ke tipe pointer apa pun.

Konversi antara dua jenis pointer tidak pernah mengubah nilai pointer aktual. Dengan kata lain, konversi dari satu jenis pointer ke jenis lainnya tidak berpengaruh pada alamat yang mendasar yang diberikan oleh pointer.

Ketika satu jenis penunjuk dikonversi ke jenis lain, jika penunjuk yang dihasilkan tidak disejajarkan dengan benar untuk jenis yang ditunjukkan, maka perilaku tidak ditentukan jika hasilnya didereferensikan. Secara umum, konsep "diratakan dengan benar" bersifat transitif: jika penunjuk ke jenis A diratakan dengan benar agar penunjuk mengetik B, yang, pada gilirannya, diratakan dengan benar agar penunjuk mengetik C, maka penunjuk ke jenis A diratakan dengan benar agar penunjuk mengetik C.

Contoh: Pertimbangkan kasus berikut di mana variabel yang memiliki satu jenis diakses melalui penunjuk ke jenis yang berbeda:

unsafe static void M()
{
    char c = 'A';
    char* pc = &c;
    void* pv = pc;
    int* pi = (int*)pv; // pretend a 16-bit char is a 32-bit int
    int i = *pi;        // read 32-bit int; undefined
    *pi = 123456;       // write 32-bit int; undefined
}

contoh akhir

Ketika tipe penunjuk dikonversi menjadi penunjuk ke byte, hasilnya menunjuk ke alamat byte terendah dari variabel. Kenaikan hasil berturut-turut, hingga ukuran variabel, menghasilkan penunjuk ke byte yang tersisa dari variabel tersebut.

Contoh: Metode berikut menampilkan masing-masing dari delapan byte dalam double dalam bentuk nilai heksadesimal:

class Test
{
    static void Main()
    {
        double d = 123.456e23;
        unsafe
        {
            byte* pb = (byte*)&d;
            for (int i = 0; i < sizeof(double); ++i)
            {
                Console.Write($" {*pb++:X2}");
            }
            Console.WriteLine();
        }
    }
}

Tentu saja, output yang dihasilkan tergantung pada endianness. Salah satu kemungkinannya adalah " BA FF 51 A2 90 6C 24 45".

contoh akhir

Pemetaan antara pointer dan bilangan bulat ditentukan implementasi.

Catatan: Namun, pada arsitektur CPU 32 dan 64-bit dengan ruang alamat linier, konversi pointer ke atau dari jenis integral biasanya bertingkah persis seperti konversi uint nilai atau ulong , masing-masing, ke atau dari jenis integral tersebut. catatan akhir

23.5.2 Array penunjuk

Array pointer dapat dibangun menggunakan array_creation_expression (§12.8.17.4) dalam konteks yang tidak aman. Hanya beberapa konversi yang berlaku untuk jenis array lainnya yang diizinkan pada array pointer.

  • Konversi referensi implisit (§10.2.8) dari array_type apa pun ke System.Array dan antarmuka yang dilaksanakannya juga berlaku untuk array pointer. Namun, setiap upaya untuk mengakses elemen array melalui System.Array atau antarmuka yang diterapkannya dapat mengakibatkan pengecualian pada run-time, karena jenis pointer tidak dapat dikonversi ke object.
  • Konversi referensi implisit dan eksplisit (§10.2.8, §10.3.5) dari jenis array satu dimensi S[] ke System.Collections.Generic.IList<T> dan antarmuka dasar generiknya tidak pernah berlaku untuk array pointer.
  • Konversi referensi eksplisit (§10.3.5) dari System.Array dan antarmuka yang diterapkannya ke array_type apa pun berlaku untuk array pointer.
  • Konversi referensi eksplisit (§10.3.5) dari System.Collections.Generic.IList<S> dan antarmuka dasarnya ke tipe array satu dimensi T[] tidak pernah berlaku untuk array pointer, karena tipe pointer tidak dapat digunakan sebagai argumen tipe, dan tidak ada konversi dari tipe pointer ke tipe non-pointer.

Pembatasan ini berarti bahwa ekspansi untuk foreach pernyataan atas array yang dijelaskan dalam §9.4.4.17 tidak dapat diterapkan ke array pointer. Sebagai gantinya, pernyataan foreach formulir

foreach (V v in x) embedded_statement

di mana jenis x adalah jenis array dalam bentuk T[,,...,], n adalah jumlah dimensi dikurangi 1 dan T atau V adalah jenis penunjuk, diperluas menggunakan perulangan bertingkat (nested for-loops) sebagai berikut:

{
    T[,,...,] a = x;
    for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++)
    {
        for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++)
        {
            ...
            for (int in = a.GetLowerBound(n); in <= a.GetUpperBound(n); in++) 
            {
                V v = (V)a[i0,i1,...,in];
                *embedded_statement*
            }
        }
    }
}

Variabel a, , i0i1, ... in tidak terlihat oleh atau dapat diakses oleh x atau embedded_statement atau kode sumber program lainnya. Variabel v bersifat baca-saja dalam pernyataan yang disematkan. Jika tidak ada konversi eksplisit (§23,5) dari T (jenis elemen) ke V, kesalahan dihasilkan dan tidak ada langkah lebih lanjut yang diambil. Jika x memiliki nilai null, System.NullReferenceException akan dilemparkan pada run-time.

Catatan: Meskipun jenis penunjuk tidak diizinkan sebagai argumen jenis, array penunjuk dapat digunakan sebagai argumen jenis. catatan akhir

23.6 Penunjuk dalam ekspresi

23.6.1 Umum

Dalam konteks tidak aman, ekspresi dapat menghasilkan hasil dari jenis penunjuk, tetapi di luar konteks yang tidak aman, terjadi kesalahan waktu kompilasi jika ekspresi menjadi jenis penunjuk. Dalam istilah yang tepat, di luar konteks yang tidak aman, kesalahan waktu kompilasi terjadi jika ada simple_name (§12.8.4), member_access (§12.8.7), invocation_expression (§12.8.10), atau element_access (§12.8.12) adalah jenis pointer.

Dalam konteks yang tidak aman, primary_expression (§12.8) dan unary_expression (§12.9) memungkinkan adanya konstruksi tambahan, yang dijelaskan dalam subklausa berikutnya.

Catatan: Prioritas dan asosiativitas operator yang tidak aman dinyatakan oleh tata bahasa. catatan akhir

23.6.2 Pointer tidak langsung

pointer_indirection_expression terdiri dari tanda bintang (*) diikuti oleh unary_expression.

pointer_indirection_expression
    : '*' unary_expression
    ;

Operator unary * menunjukkan tidak langsung penunjuk dan digunakan untuk mendapatkan variabel tempat titik penunjuk. Hasil evaluasi *P, di mana P adalah ekspresi dari jenis T*penunjuk , adalah variabel jenis T. Ini adalah kesalahan waktu kompilasi untuk menerapkan operator unary * ke ekspresi jenis void* atau ke ekspresi yang bukan dari jenis penunjuk.

Efek dari penerapan operator unary * ke pointer bernilai null ditentukan oleh implementasi. Secara khusus, tidak ada jaminan bahwa operasi ini akan melemparkan System.NullReferenceException.

Jika nilai yang tidak valid telah ditetapkan ke penunjuk, perilaku operator unary * tidak terdefinisi.

Catatan: Di antara nilai yang tidak valid untuk mendereferensikan pointer oleh operator unary * adalah alamat yang secara tidak tepat diselaraskan untuk jenis yang diarahkan ke (lihat contoh di §23,5), dan alamat variabel setelah akhir masa pakainya.

Untuk tujuan analisis penetapan pasti, variabel yang dihasilkan dengan mengevaluasi ekspresi formulir *P dianggap awalnya ditetapkan (§9.4.2).

23.6.3 Akses anggota pointer

pointer_member_access terdiri dari primary_expression, diikuti dengan token ->, diikuti oleh pengidentifikasi dan type_argument_list opsional.

pointer_member_access
    : primary_expression '->' identifier type_argument_list?
    ;

Dalam akses anggota penunjuk bentuk P->I, P harus berupa ekspresi dari tipe penunjuk, dan I harus menunjukkan anggota yang dapat diakses dari tipe yang ditunjuk oleh P.

Akses anggota pointer formulir P->I dievaluasi persis seperti (*P).I. Untuk deskripsi operator tidak langsung pointer (*), lihat §23.6.2. Untuk deskripsi operator akses anggota (.), lihat §12.8.7.

Contoh: Dalam kode berikut

struct Point
{
    public int x;
    public int y;
    public override string ToString() => $"({x},{y})";
}

class Test
{
    static void Main()
    {
        Point point;
        unsafe
        {
            Point* p = &point;
            p->x = 10;
            p->y = 20;
            Console.WriteLine(p->ToString());
        }
    }
}

-> operator digunakan untuk mengakses bidang dan memanggil metode struct melalui pointer. Karena operasi P->I ini tepatnya setara dengan (*P).I, Main metode ini bisa ditulis dengan cara yang sama.

class Test
{
    static void Main()
    {
        Point point;
        unsafe
        {
            Point* p = &point;
            (*p).x = 10;
            (*p).y = 20;
            Console.WriteLine((*p).ToString());
        }
    }
}

contoh akhir

23.6.4 Akses elemen Pointer

pointer_element_access terdiri dari primary_expression diikuti dengan ekspresi yang diapit oleh tanda kurung "[" dan "]".

pointer_element_access
    : primary_expression '[' expression ']'
    ;

Saat mengidentifikasi primary_expression, jika kedua alternatif element_access dan pointer_element_access (§23.6.4) berlaku, maka yang terakhir harus dipilih apabila primary_expression yang tertanam adalah tipe pointer (§23.3).

Dalam akses elemen penunjuk dari formulir P[E], P harus merupakan ekspresi dari jenis pointer selain void*, dan E harus menjadi ekspresi yang dapat dikonversi secara implisit ke int, uint, long, atau ulong.

Akses elemen penunjuk formulir P[E] dievaluasi persis seperti *(P + E). Untuk deskripsi operator tidak langsung pointer (*), lihat §23.6.2. Untuk deskripsi operator penambahan pointer (+), lihat §23.6.7.

Contoh: Dalam kode berikut

class Test
{
    static void Main()
    {
        unsafe
        {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++)
            {
                p[i] = (char)i;
            }
        }
    }
}

akses elemen pointer digunakan untuk menginisialisasi buffer karakter dalam perulangan for . Karena operasi P[E] ini tepatnya setara dengan *(P + E), contohnya bisa sama baiknya telah ditulis:

class Test
{
    static void Main()
    {
        unsafe
        {
            char* p = stackalloc char[256];
            for (int i = 0; i < 256; i++)
            {
                *(p + i) = (char)i;
            }
        }
    }
}

contoh akhir

Operator akses elemen penunjuk tidak memeriksa kesalahan di luar batas dan perilaku saat mengakses elemen di luar batas tidak terdefinisi.

Catatan: Ini sama dengan C dan C++. catatan akhir

23.6.5 Alamat operator

Addressof_expression terdiri dari ampersand (&) diikuti oleh unary_expression.

addressof_expression
    : '&' unary_expression
    ;

Mengingat ekspresi E yang berjenis T dan diklasifikasikan sebagai variabel tetap (§23.4), konstruksi &E menghitung alamat variabel yang diberikan oleh E. Jenis hasilnya adalah T* dan diklasifikasikan sebagai nilai. Kesalahan waktu kompilasi terjadi jika E tidak diklasifikasikan sebagai variabel, jika E diklasifikasikan sebagai variabel lokal baca-saja, atau jika E menunjukkan variabel yang dapat dipindahkan. Dalam kasus terakhir, pernyataan tetap (§23.7) dapat digunakan untuk sementara "memperbaiki" variabel sebelum mendapatkan alamatnya.

Catatan: Seperti yang dinyatakan dalam §12.8.7, di luar konstruktor instans readonly atau konstruktor statis untuk struktur atau kelas yang mendefinisikan bidang, bidang tersebut dianggap sebagai nilai, bukan variabel. Dengan demikian, alamatnya tidak dapat dijangkau. Demikian pula, alamat konstanta tidak dapat diambil. catatan akhir

Operator & tidak memerlukan argumennya untuk secara pasti ditetapkan, tetapi setelah & operasi, variabel tempat operator diterapkan dianggap pasti ditetapkan dalam jalur eksekusi tempat operasi terjadi. Adalah tanggung jawab programmer untuk memastikan bahwa inisialisasi variabel yang benar benar benar benar benar terjadi dalam situasi ini.

Contoh: Dalam kode berikut

class Test
{
    static void Main()
    {
        int i;
        unsafe
        {
            int* p = &i;
            *p = 123;
        }
        Console.WriteLine(i);
    }
}

i dianggap pasti ditetapkan setelah operasi &i digunakan untuk menginisialisasi p. Penugasan untuk *p berlaku menginisialisasi i, tetapi dimasukkannya inisialisasi ini adalah tanggung jawab programmer, dan tidak ada kesalahan waktu kompilasi yang akan terjadi jika penugasan dihapus.

contoh akhir

Catatan: Aturan penetapan pasti untuk & operator ada sehingga inisialisasi variabel lokal yang berlebihan dapat dihindari. Misalnya, banyak API eksternal menerima penunjuk ke sebuah struktur yang kemudian diisi oleh API tersebut. Panggilan ke API tersebut biasanya meneruskan alamat dari variabel struct lokal, dan tanpa adanya aturan tersebut, inisialisasi redundan dari variabel struct akan diperlukan. catatan akhir

Catatan: Ketika variabel lokal, parameter nilai, atau array parameter ditangkap oleh fungsi anonim (§12.8.24), variabel lokal, parameter, atau array parameter tidak lagi dianggap sebagai variabel tetap (§23,7), tetapi dianggap sebagai variabel yang dapat dipindahkan. Dengan demikian, ini adalah kesalahan bagi kode yang tidak aman untuk mengambil alamat variabel lokal, parameter nilai, atau array parameter yang telah ditangkap oleh fungsi anonim. catatan akhir

23.6.6 Penunjuk kenaikan dan penurunan

Dalam konteks yang tidak aman, ++ operator dan -- (§12.8.16 dan §12.9.6) dapat diterapkan ke variabel pointer dari semua jenis kecuali void*. Dengan demikian, untuk setiap jenis T*pointer , operator berikut secara implisit ditentukan:

T* operator ++(T* x);
T* operator --(T* x);

Operator menghasilkan hasil yang sama dengan x+1 dan x-1, masing-masing (§23.6.7). Dengan kata lain, untuk variabel pointer jenis T*, operator menambahkan ++ ke alamat yang terkandung dalam variabel, dan sizeof(T) operator mengurangi -- dari alamat yang terkandung sizeof(T) dalam variabel.

Jika operasi kenaikan atau penurunan pointer melampaui batas domain jenis pointer, hasilnya bergantung pada implementasi, tetapi tidak ada pengecualian yang terjadi.

23.6.7 Aritmatika pointer

Dalam konteks tidak aman, operator + (§12.10.5) dan operator - (§12.10.6) dapat diterapkan ke nilai semua jenis pointer kecuali void*. Dengan demikian, untuk setiap jenis T*pointer , operator berikut secara implisit ditentukan:

T* operator +(T* x, int y);
T* operator +(T* x, uint y);
T* operator +(T* x, long y);
T* operator +(T* x, ulong y);
T* operator +(int x, T* y);
T* operator +(uint x, T* y);
T* operator +(long x, T* y);
T* operator +(ulong x, T* y);
T* operator –(T* x, int y);
T* operator –(T* x, uint y);
T* operator –(T* x, long y);
T* operator –(T* x, ulong y);
long operator –(T* x, T* y);

Mengacu pada ekspresi jenis penunjuk P dan ekspresi jenis T*N, int, uint, atau long, ekspresi ulong dan P + N menghitung nilai jenis penunjuk N + P yang dihasilkan dari penambahan T* ke alamat yang diberikan oleh N * sizeof(T). Demikian juga, ekspresi P – N menghitung nilai penunjuk jenis T* yang dihasilkan dari pengurangan N * sizeof(T) dari alamat yang diberikan oleh P.

Mengingat dua ekspresi, P dan Q, dari jenis T*penunjuk , ekspresi P – Q menghitung perbedaan antara alamat yang diberikan oleh P dan Q kemudian membagi perbedaan tersebut dengan sizeof(T). Jenis hasilnya selalu long. Akibatnya, P - Q dihitung sebagai ((long)(P) - (long)(Q)) / sizeof(T).

Contoh:

class Test
{
    static void Main()
    {
        unsafe
        {
            int* values = stackalloc int[20];
            int* p = &values[1];
            int* q = &values[15];
            Console.WriteLine($"p - q = {p - q}");
            Console.WriteLine($"q - p = {q - p}");
        }
    }
}

yang menghasilkan output:

p - q = -14
q - p = 14

contoh akhir

Jika operasi aritmatika pointer melampaui batas domain jenis pointer, hasilnya dipotong dengan cara yang ditentukan oleh implementasi, tetapi tidak ada pengecualian yang terjadi.

23.6.8 Perbandingan penunjuk

Dalam konteks yang tidak aman, operator ==, !=, <, >, <=, dan >= (§12.12) dapat diterapkan ke nilai semua jenis pointer. Operator perbandingan pointer adalah:

bool operator ==(void* x, void* y);
bool operator !=(void* x, void* y);
bool operator <(void* x, void* y);
bool operator >(void* x, void* y);
bool operator <=(void* x, void* y);
bool operator >=(void* x, void* y);

Karena konversi implisit ada dari semua jenis pointer ke jenis void*, semua operand jenis pointer dapat dibandingkan menggunakan operator ini. Operator perbandingan membandingkan alamat yang diberikan oleh dua operan seolah-olah mereka bilangan bulat yang tidak ditandatangani.

23.6.9 Operator sizeof

Untuk jenis tertentu yang telah ditentukan sebelumnya (§12.8.19), sizeof operator menghasilkan nilai konstanta int . Untuk semua jenis lainnya, hasil operator sizeof ditentukan implementasi dan diklasifikasikan sebagai nilai, bukan konstanta.

Urutan di mana anggota diatur ke dalam struktur tidak ditentukan.

Untuk tujuan penyelarasan, mungkin ada padding yang tidak disebutkan namanya di awal struct, dalam struct, dan di akhir struct. Isi bit yang digunakan sebagai padding tidak ditentukan.

Saat diterapkan ke operand yang memiliki jenis struct, hasilnya adalah jumlah total byte dalam variabel jenis tersebut, termasuk padding apa pun.

23.7 Pernyataan tetap

Dalam konteks yang tidak aman, produksi embedded_statement (§13.1) mengizinkan konstruksi tambahan, pernyataan tetap, yang digunakan untuk "memperbaiki" variabel yang dapat dipindahkan sehingga alamatnya tetap konstan selama durasi pernyataan.

fixed_statement
    : 'fixed' '(' pointer_type fixed_pointer_declarators ')' embedded_statement
    ;

fixed_pointer_declarators
    : fixed_pointer_declarator (','  fixed_pointer_declarator)*
    ;

fixed_pointer_declarator
    : identifier '=' fixed_pointer_initializer
    ;

fixed_pointer_initializer
    : '&' variable_reference
    | expression
    ;

Setiap fixed_pointer_declarator mendeklarasikan variabel lokal dari pointer_type yang diberikan dan menginisialisasi variabel lokal tersebut dengan alamat yang dihitung oleh fixed_pointer_initializer yang sesuai. Variabel lokal yang dideklarasikan dalam pernyataan tetap dapat diakses dalam fixed_pointer_initializer apa pun yang muncul di sebelah kanan deklarasi variabel tersebut, dan dalam embedded_statement dari pernyataan tetap. Variabel lokal yang dideklarasikan oleh pernyataan tetap dianggap baca-saja. Kesalahan waktu kompilasi terjadi jika pernyataan yang disematkan mencoba memodifikasi variabel lokal ini (melalui penugasan atau ++ operator dan -- ) atau meneruskannya sebagai parameter referensi atau output.

Ini adalah kesalahan untuk menggunakan variabel lokal yang tertangkap (§12.19.6.2), parameter nilai, atau array parameter dalam fixed_pointer_initializer. Fixed_pointer_initializer dapat berupa salah satu dari berikut:

  • Token "&" diikuti oleh variable_reference (§9,5) ke variabel yang dapat dipindahkan (§23,4) dari jenis Tyang tidak dikelola , asalkan jenis T* secara implisit dapat dikonversi ke jenis pointer yang diberikan dalam fixed pernyataan. Dalam hal ini, penginisialisasi menghitung alamat variabel yang diberikan, dan variabel dijamin akan tetap berada di alamat tetap selama durasi pernyataan tetap.
  • Ekspresi array_type dengan elemen tipe yang tidak terkelola, asalkan jenisnya T dapat dikonversi secara implisit ke tipe penunjuk yang diberikan dalam pernyataan yang tetap. Dalam hal ini, penginisialisasi menghitung alamat elemen pertama dalam array, dan seluruh array dijamin tetap pada alamat tetap selama durasi fixed pernyataan. Jika ekspresi array adalah null atau jika array memiliki elemen nol, penginisialisasi menghitung alamat yang sama dengan nol.
  • Ekspresi tipe string, dengan ketentuan tipe char* secara implisit dapat dikonversi ke tipe pointer yang disebut dalam pernyataan fixed. Dalam hal ini, penginisialisasi menghitung alamat karakter pertama dalam string, dan seluruh string dijamin akan tetap berada di alamat tetap selama durasi fixed pernyataan. Perilaku pernyataan fixed ditentukan oleh implementasi jika ekspresi string adalah null.
  • Ekspresi jenis selain array_type atau string, asalkan ada metode yang dapat diakses atau metode ekstensi yang dapat diakses yang cocok dengan tanda tangan ref [readonly] T GetPinnableReference(), di mana T adalah unmanaged_type, dan T* secara implisit dapat dikonversi ke jenis penunjuk yang diberikan dalam pernyataan fixed. Dalam hal ini, penginisialisasi menghitung alamat variabel yang dikembalikan, dan variabel tersebut dijamin akan tetap berada di alamat tetap selama durasi fixed pernyataan. Metode GetPinnableReference() dapat digunakan oleh fixed statemen ketika resolusi overload (§12.6.4) menghasilkan tepat satu anggota fungsi, dan anggota fungsi tersebut memenuhi kondisi sebelumnya. Metode GetPinnableReference harus mengembalikan referensi ke alamat yang sama dengan nol, seperti yang dikembalikan dari System.Runtime.CompilerServices.Unsafe.NullRef<T>() ketika tidak ada data yang akan disematkan.
  • simple_name atau member_access yang mereferensikan anggota buffer ukuran tetap dari variabel yang dapat dipindahkan, asalkan jenis anggota buffer ukuran tetap secara implisit dapat dikonversi ke jenis penunjuk yang diberikan dalam fixed pernyataan. Dalam hal ini, penginisialisasi menghitung penunjuk ke elemen pertama dari buffer ukuran tetap (§23.8.3), dan buffer ukuran tetap dijamin berada di alamat yang sama selama pernyataan fixed berlangsung.

Untuk setiap alamat yang dihitung dalam fixed_pointer_initializer, perintah fixed memastikan bahwa variabel yang direferensikan oleh alamat ini tidak tunduk pada relokasi atau pembuangan oleh pengumpul sampah selama durasi perintah fixed.

Contoh: Jika alamat yang dihitung oleh fixed_pointer_initializer mereferensikan bidang objek atau elemen instans array, pernyataan tetap menjamin bahwa instans objek yang berisi tidak direlokasi atau dibuang selama masa pakai pernyataan. contoh akhir

Programmer bertanggung jawab untuk memastikan bahwa pointer yang dibuat oleh pernyataan fixed tidak bertahan setelah eksekusi pernyataan tersebut.

Contoh: Ketika pointer yang dibuat oleh fixed pernyataan diteruskan ke API eksternal, programmer bertanggung jawab untuk memastikan bahwa API tidak mempertahankan memori pointer ini. contoh akhir

Objek tetap dapat menyebabkan fragmentasi tumpukan (karena tidak dapat dipindahkan). Untuk alasan itu, objek harus diperbaiki hanya ketika benar-benar diperlukan dan kemudian hanya untuk jumlah waktu sesingkat mungkin.

Contoh: Contoh

class Test
{
    static int x;
    int y;

    unsafe static void F(int* p)
    {
        *p = 1;
    }

    static void Main()
    {
        Test t = new Test();
        int[] a = new int[10];
        unsafe
        {
            fixed (int* p = &x) F(p);
            fixed (int* p = &t.y) F(p);
            fixed (int* p = &a[0]) F(p);
            fixed (int* p = a) F(p);
        }
    }
}

menunjukkan beberapa penggunaan pernyataan fixed. Pernyataan pertama memperbaiki dan mendapatkan alamat bidang statis, pernyataan kedua memperbaiki dan mendapatkan alamat bidang instans, dan pernyataan ketiga memperbaiki dan mendapatkan alamat elemen array. Dalam setiap kasus, itu akan menjadi kesalahan untuk menggunakan operator reguler & karena variabel semuanya diklasifikasikan sebagai variabel yang dapat dipindahkan.

Pernyataan ketiga dan keempat fixed dalam contoh di atas menghasilkan hasil yang identik. Secara umum, untuk instans array a, menentukan a[0] pada pernyataan fixed sama dengan hanya menentukan a.

contoh akhir

Dalam konteks yang tidak aman, elemen array berdimensi tunggal disimpan dalam urutan indeks yang meningkat, dimulai dengan indeks 0 dan diakhiri dengan indeks Length – 1. Untuk array multidimensi, elemen array disimpan sedih sehingga indeks dimensi paling kanan ditingkatkan terlebih dahulu, lalu dimensi kiri berikutnya, dan seterusnya di sebelah kiri.

fixed Dalam pernyataan yang mendapatkan penunjuk p ke instans aarray , nilai penunjuk mulai dari p untuk p + a.Length - 1 mewakili alamat elemen dalam array. Demikian juga, variabel mulai dari p[0] untuk p[a.Length - 1] mewakili elemen array aktual. Mengingat cara penyimpanan array, array dalam dimensi apapun dapat diperlakukan seolah-olah berbentuk linier.

Contoh:

class Test
{
    static void Main()
    {
        int[,,] a = new int[2,3,4];
        unsafe
        {
            fixed (int* p = a)
            {
                for (int i = 0; i < a.Length; ++i) // treat as linear
                {
                    p[i] = i;
                }
            }
        }
        for (int i = 0; i < 2; ++i)
        {
            for (int j = 0; j < 3; ++j)
            {
                for (int k = 0; k < 4; ++k)
                {
                    Console.Write($"[{i},{j},{k}] = {a[i,j,k],2} ");
                }
                Console.WriteLine();
            }
        }
    }
}

yang menghasilkan output:

[0,0,0] =  0 [0,0,1] =  1 [0,0,2] =  2 [0,0,3] =  3
[0,1,0] =  4 [0,1,1] =  5 [0,1,2] =  6 [0,1,3] =  7
[0,2,0] =  8 [0,2,1] =  9 [0,2,2] = 10 [0,2,3] = 11
[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15
[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19
[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23

contoh akhir

Contoh: Dalam kode berikut

class Test
{
    unsafe static void Fill(int* p, int count, int value)
    {
        for (; count != 0; count--)
        {
            *p++ = value;
        }
    }

    static void Main()
    {
        int[] a = new int[100];
        unsafe
        {
            fixed (int* p = a) Fill(p, 100, -1);
        }
    }
}

fixed pernyataan digunakan untuk memperbaiki array sehingga alamatnya dapat diteruskan ke metode yang mengambil pointer.

contoh akhir

char* Nilai yang dihasilkan dengan mengunci instans string selalu menunjuk ke string yang diakhiri dengan null. Dalam pernyataan tetap yang mendapatkan pointer p ke instans string s, nilai penunjuk mulai dari p hingga p + s.Length ‑ 1 mewakili alamat karakter dalam string, dan nilai penunjuk p + s.Length selalu menunjuk ke karakter null (karakter dengan nilai '\0').

Contoh:

class Test
{
    static string name = "xx";

    unsafe static void F(char* p)
    {
        for (int i = 0; p[i] != '\0'; ++i)
        {
            System.Console.WriteLine(p[i]);
        }
    }

    static void Main()
    {
        unsafe
        {
            fixed (char* p = name) F(p);
            fixed (char* p = "xx") F(p);
        }
    }
}

contoh akhir

Contoh: Kode berikut menunjukkan fixed_pointer_initializer dengan ekspresi jenis selain array_type atau string:

public class C
{
    private int _value;
    public C(int value) => _value = value;
    public ref int GetPinnableReference() => ref _value;
}

public class Test
{
    unsafe private static void Main()
    {
        C c = new C(10);
        fixed (int* p = c)
        {
            // ...
        }
    }
}

Jenis C memiliki metode yang dapat diakses GetPinnableReference dengan tanda tangan yang benar. fixed Dalam pernyataan, yang ref int dikembalikan dari metode itu ketika dipanggil c digunakan untuk menginisialisasi int* pointer p. contoh akhir

Memodifikasi objek jenis terkelola melalui penunjuk tetap dapat mengakibatkan perilaku yang tidak ditentukan.

Catatan: Misalnya, karena string tidak dapat diubah, programmer bertanggung jawab untuk memastikan bahwa karakter yang dirujuk oleh pointer menuju string tetap tersebut tidak dimodifikasi. catatan akhir

Catatan: Penutupan null otomatis dari string sangat berguna ketika memanggil API eksternal yang mengharapkan string "gaya C". Namun, perhatikan bahwa instans string diizinkan untuk berisi karakter null. Jika karakter null tersebut ada, string akan muncul terpotong ketika diperlakukan sebagai null-terminated char*. catatan akhir

23.8 Buffer ukuran tetap

23.8.1 Umum

Buffer ukuran tetap digunakan untuk mendeklarasikan array in-line "gaya C" sebagai anggota struct, dan terutama berguna untuk berinteraksi dengan API yang tidak dikelola.

23.8.2 Deklarasi buffer ukuran tetap

Buffer ukuran tetap adalah anggota yang mewakili penyimpanan untuk buffer variabel dengan panjang tetap dari jenis tertentu. Deklarasi buffer ukuran tetap memperkenalkan satu atau beberapa buffer ukuran tetap dari jenis elemen tertentu.

Catatan: Seperti array, buffer ukuran tetap dapat dianggap sebagai mengandung elemen. Dengan demikian, istilah jenis elemen, seperti yang didefinisikan untuk array, juga digunakan dengan buffer ukuran tetap. catatan akhir

Buffer ukuran tetap hanya diizinkan dalam deklarasi struktur dan hanya dapat terjadi dalam konteks yang tidak aman (§23.2).

fixed_size_buffer_declaration
    : attributes? fixed_size_buffer_modifier* 'fixed' buffer_element_type
      fixed_size_buffer_declarators ';'
    ;

fixed_size_buffer_modifier
    : 'new'
    | 'public'
    | 'internal'
    | 'private'
    | 'unsafe'
    ;

buffer_element_type
    : type
    ;

fixed_size_buffer_declarators
    : fixed_size_buffer_declarator (',' fixed_size_buffer_declarator)*
    ;

fixed_size_buffer_declarator
    : identifier '[' constant_expression ']'
    ;

Deklarasi buffer ukuran tetap dapat mencakup sekumpulan atribut (§22), new pengubah (§15.3.5), pengubah aksesibilitas sesuai dengan salah satu aksesibilitas yang dinyatakan yang diizinkan untuk anggota struct (§16.4.3) dan pengubah unsafe (§23.2). Atribut dan pengubah ini berlaku untuk semua anggota yang diumumkan oleh deklarasi buffer berukuran tetap. Ini adalah kesalahan jika pengubah yang sama muncul beberapa kali dalam deklarasi buffer ukuran tetap.

Deklarasi buffer ukuran tetap tidak diizinkan untuk menyertakan pengubah static .

Jenis elemen buffer pada deklarasi buffer berukuran tetap menentukan tipe elemen buffer yang ditetapkan oleh deklarasi tersebut. Jenis elemen buffer harus menjadi salah satu jenis sbyteyang telah ditentukan sebelumnya , , , byteshortushortintuintlong, ulong, char, float, doubleatau .bool

Jenis elemen buffer diikuti oleh daftar deklarator buffer ukuran tetap, yang masing-masing memperkenalkan anggota baru. Deklarator buffer ukuran tetap terdiri dari pengidentifikasi yang menamai anggota, diikuti dengan ekspresi konstanta yang diapit dan [] token. Ekspresi konstanta menunjukkan jumlah elemen dalam anggota yang diperkenalkan oleh deklarator buffer ukuran tetap tersebut. Jenis ekspresi konstanta harus secara implisit dapat dikonversi ke jenis int, dan nilainya adalah bilangan bulat positif bukan nol.

Elemen buffer ukuran tetap harus ditata secara berurutan dalam memori.

Deklarasi buffer berukuran tetap yang mendeklarasikan beberapa buffer dengan ukuran tetap setara dengan deklarasi tunggal buffer berukuran tetap yang memiliki atribut dan jenis elemen yang sama.

Contoh:

unsafe struct A
{
    public fixed int x[5], y[10], z[100];
}

setara dengan:

unsafe struct A
{
    public fixed int x[5];
    public fixed int y[10];
    public fixed int z[100];
}

contoh akhir

23.8.3 Buffer ukuran tetap dalam ekspresi

Pencarian anggota (§12.5) dari buffer berukuran tetap berlanjut sama seperti pencarian anggota bidang.

Buffer ukuran tetap dapat direferensikan dalam ekspresi menggunakan simple_name (§12.8.4), member_access (§12.8.7), atau element_access (§12.8.12).

Ketika anggota buffer ukuran tetap direferensikan sebagai nama sederhana, efeknya sama dengan akses anggota formulir this.I, di mana I adalah anggota buffer ukuran tetap.

Dalam akses anggota dalam bentuk E.I di mana E. mungkin implisit this., jika E bertipe struct dan pencarian anggota I dalam tipe struct tersebut mengarah ke anggota berukuran tetap, maka E.I dievaluasi dan diklasifikasikan sebagai berikut:

  • Jika ekspresi E.I tidak terjadi dalam konteks yang tidak aman, kesalahan waktu kompilasi terjadi.
  • Jika E diklasifikasikan sebagai nilai, kesalahan waktu kompilasi terjadi.
  • Jika tidak, jika E adalah variabel yang dapat dipindahkan (§23.4) maka:
    • Jika ekspresi E.I merupakan fixed_pointer_initializer (§23.7), maka hasil dari ekspresi tersebut adalah sebuah penunjuk ke elemen pertama dari anggota buffer berukuran tetap I di E.
    • Sebaliknya, jika ekspresi E.I adalah primary_expression (§12.8.12.1) dalam bentuk dari element_access (E.I[J]), maka hasil dari E.I berupa pointer, P, ke elemen pertama dalam anggota buffer berukuran tetap I di E, dan element_access penutup kemudian dievaluasi sebagai pointer_element_access (§23.6.4) P[J].
    • Jika tidak, kesalahan waktu kompilasi terjadi.
  • Jika tidak, E mereferensikan variabel yang tetap dan hasil ekspresi adalah pointer ke elemen pertama anggota buffer ukuran tetap I di E. Hasilnya adalah tipe S*, di mana S adalah tipe elemen I, dan diklasifikasikan sebagai sebuah nilai.

Elemen-elemen berikutnya dari buffer ukuran tetap dapat diakses menggunakan operasi pointer berdasarkan elemen pertama. Tidak seperti akses ke array, akses ke elemen buffer ukuran tetap adalah operasi yang tidak aman dan tidak dilakukan pemeriksaan rentang.

Contoh: Berikut mendeklarasikan dan menggunakan struct dengan anggota buffer ukuran tetap.

unsafe struct Font
{
    public int size;
    public fixed char name[32];
}

class Test
{
    unsafe static void PutString(string s, char* buffer, int bufSize)
    {
        int len = s.Length;
        if (len > bufSize)
        {
            len = bufSize;
        }
        for (int i = 0; i < len; i++)
        {
            buffer[i] = s[i];
        }
        for (int i = len; i < bufSize; i++)
        {
            buffer[i] = (char)0;
        }
    }

    unsafe static void Main()
    {
        Font f;
        f.size = 10;
        PutString("Times New Roman", f.name, 32);
    }
}

contoh akhir

23.8.4 Pemeriksaan penetapan pasti

Buffer ukuran tetap tidak tunduk pada pemeriksaan penetapan pasti (§9.4), dan anggota buffer ukuran tetap diabaikan untuk tujuan pada pemeriksaan penetapan pasti variabel jenis struct.

Ketika variabel struct yang berada di paling luar dari anggota buffer ukuran tetap adalah variabel statis, variabel instance dari instance kelas, atau elemen array, elemen buffer ukuran tetap secara otomatis diinisialisasi dengan nilai default mereka (§9.3). Dalam semua kasus lain, konten awal dari buffer ukuran tetap tidak ditentukan.

23.9 Pengalokasian tumpukan

Lihat §12.8.22 untuk informasi umum tentang operator stackalloc. Di sini, kemampuan operator tersebut untuk menghasilkan pointer dibahas.

Ketika stackalloc_expression terjadi sebagai ekspresi inisialisasi dari local_variable_declaration (§13.6.2), di mana local_variable_type adalah tipe pointer (§23.3) atau yang disimpulkan (var), hasil dari stackalloc_expression adalah pointer dari tipe T*, di mana T adalah unmanaged_type dari stackalloc_expression. Dalam hal ini, hasilnya adalah penunjuk yang menunjukkan awal dari blok yang dialokasikan.

Contoh:

unsafe 
{
    // Memory uninitialized
    int* p1 = stackalloc int[3];
    // Memory initialized
    int* p2 = stackalloc int[3] { -10, -15, -30 };
    // Type int is inferred
    int* p3 = stackalloc[] { 11, 12, 13 };
    // Can't infer context, so pointer result assumed
    var p4 = stackalloc[] { 11, 12, 13 };
    // Error; no conversion exists
    long* p5 = stackalloc[] { 11, 12, 13 };
    // Converts 11 and 13, and returns long*
    long* p6 = stackalloc[] { 11, 12L, 13 };
    // Converts all and returns long*
    long* p7 = stackalloc long[] { 11, 12, 13 };
}

contoh akhir

Tidak seperti akses ke array atau blok stackalloc yang didasarkan pada tipe Span<T>, akses ke elemen dari blok stackalloc yang didasarkan pada tipe penunjuk adalah operasi yang tidak aman dan tidak diperiksa rentangnya.

Contoh: Dalam kode berikut

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        int n = value >= 0 ? value : -value;
        unsafe
        {
            char* buffer = stackalloc char[16];
            char* p = buffer + 16;
            do
            {
                *--p = (char)(n % 10 + '0');
                n /= 10;
            } while (n != 0);
            if (value < 0)
            {
                *--p = '-';
            }
            return new string(p, 0, (int)(buffer + 16 - p));
        }
    }

    static void Main()
    {
        Console.WriteLine(IntToString(12345));
        Console.WriteLine(IntToString(-999));
    }
}

stackalloc ekspresi digunakan dalam IntToString metode untuk mengalokasikan buffer 16 karakter pada tumpukan. Buffer secara otomatis dibuang setelah fungsi selesai.

Namun, perhatikan bahwa IntToString dapat ditulis ulang dalam mode aman; yaitu, tanpa menggunakan pointer, sebagai berikut:

class Test
{
    static string IntToString(int value)
    {
        if (value == int.MinValue)
        {
            return "-2147483648";
        }
        int n = value >= 0 ? value : -value;
        Span<char> buffer = stackalloc char[16];
        int idx = 16;
        do
        {
            buffer[--idx] = (char)(n % 10 + '0');
            n /= 10;
        } while (n != 0);
        if (value < 0)
        {
            buffer[--idx] = '-';
        }
        return buffer.Slice(idx).ToString();
    }
}

contoh akhir

Akhir teks normatif kondisional.