Antara lain, C99 menambah kata kunci sekatan sebagai cara untuk pengaturcara menentukan bahawa penunjuk ialah sahaja penunjuk kepada objek tertentu dalam skop dan, akibatnya, berikan pengkompil "petunjuk ” bahawa ia boleh melakukan pengoptimuman tambahan apabila mengakses objek melalui penuding itu.
Untuk menggambarkan masalah yang ingin diselesaikan sekatan, pertimbangkan fungsi seperti:
void update_ptrs( int *p, int *q, int const *v ) { *p += *v; *q += *v; }
yang mana pengkompil akan menjana kod x86-64 seperti:
mov eax, [rdx] ; tmp = *v // 1 add [rdi], eax ; *p += tmp mov eax, [rdx] ; tmp = *v // 3 add [rsi], eax ; *q += tmp
Anda mungkin tertanya-tanya mengapa ia menjana baris 3 kerana ia kelihatan berlebihan dengan baris 1. Masalahnya ialah pengkompil tidak dapat mengetahui bahawa anda tidak melakukan sesuatu seperti ini:
int x = 1, v = 2; update_ptrs( &v, &x, &v ); // x = 5, v = 4
Dalam update_ptrs(), p dan v akan alias int sama, jadi pengkompil perlu memainkannya dengan selamat dan menganggap bahawa nilai *v boleh berubah antara bacaan, maka arahan mov tambahan.
Secara amnya, penunjuk dalam C mengelirukan pengoptimuman kerana pengkompil tidak dapat mengetahui sama ada dua penunjuk alias antara satu sama lain. Dalam kod kritikal prestasi, menghapuskan memori dibaca boleh menjadi kemenangan besar jika pengkompil boleh melakukannya dengan selamat.
Untuk menyelesaikan masalah yang disebutkan di atas, had telah ditambahkan pada C untuk membolehkan anda menentukan bahawa penunjuk yang diberikan ialah sahaja penunjuk kepada objek dalam skop penunjuk, iaitu, tiada penuding lain dalam alias skop yang sama ia.
Untuk menggunakan sekatan, anda memasukkannya antara * dan nama penuding dalam pengisytiharan. Update_ptrs() yang ditulis semula untuk menggunakan sekatan ialah:
void update_ptrs_v2( int *restrict p, int *restrict q, int const *restrict v ) { *p += *v; *q += *v; }
(Baca dari kanan ke kiri, cth., v ialah penunjuk terhad kepada int pemalar; atau gunakan cdecl.)
Dengan menambahkan sekatan, pengkompil kini boleh menjana kod seperti:
mov eax, [rdx] ; tmp = *v add [rdi], eax ; *p += tmp add [rsi], eax ; *q += tmp
Kini, pengkompil dapat menghilangkan baris 3 sebelumnya bagi arahan mov tambahan.
Mungkin contoh paling terkenal di mana sekatan digunakan ialah fungsi perpustakaan standard memcpy(). Ini adalah cara terpantas untuk menyalin sebahagian daripada memori jika alamat sumber dan destinasi tidak bertindih. Fungsi memmove() yang sedikit perlahan wujud untuk digunakan apabila alamat lakukan bertindih.
Penyalahgunaan sekatan mengakibatkan tingkah laku yang tidak ditentukan, sebagai contoh, dengan memberikan petunjuk yang melakukan alias antara satu sama lain kepada update_ptrs_v2() atau memcpy(). Dalam beberapa kes, pengkompil boleh memberi amaran kepada anda, tetapi tidak dalam semua kes, jadi jangan bergantung pada pengkompil untuk menangkap penyalahgunaan.
Perhatikan bahawa sekatan adalah untuk skop tertentu. Menugaskan satu penuding terhad kepada yang lain dalam skop yang sama menghasilkan tingkah laku yang tidak ditentukan:
void f( int *restrict d, int *restrict s ) { int *restrict p = s; // undefined behavior
Walau bagaimanapun, anda boleh menetapkan penunjuk terhad kepada penuding tidak terhad dengan baik:
void f( int *restrict d, int *restrict s ) { int *p = s; // OK
Walaupun p tidak terhad, pengkompil masih boleh melakukan pengoptimuman yang sama.
Adalah OK untuk memberikan penuding terhad dalam skop dalaman kepada yang lain dalam skop luar (tetapi bukan sebaliknya):
void f( int *restrict d, int *restrict s ) { { // inner scope int *restrict p = s; // OK // ... s = p; // undefined behavior } }
Pertama sekali, anda mesti memprofilkan kod anda (dan mungkin juga melihat pada kod pemasangan yang dijana) untuk melihat sama ada menggunakan sekatan benar-benar membuat peningkatan prestasi ketara untuk mewajarkan risiko kemungkinan perangkap. Mendiagnosis pepijat yang disebabkan oleh penyalahgunaan sekatan adalah sangat sukar dilakukan.
Kedua, jika penggunaan sekatan terhad kepada pelaksanaan fungsi di mana memori yang diakses melalui penunjuk terhad telah diperuntukkan oleh anda, maka ia adalah lebih selamat. Contohnya, diberikan:
void safer( unsigned n ) { n += n % 2 != 0; // make even by rounding up int *const array = malloc( n * sizeof(unsigned) ); unsigned *restrict half_1st = array; unsigned *restrict half_2nd = array + n/2; // ... free( array ); }
kod boleh beroperasi pada bahagian pertama dan kedua tatasusunan dengan selamat kerana ia tidak bertindih (dengan andaian anda tidak pernah mengakses separuh_1[n/2] atau seterusnya).
Ketiga, jika sekatan digunakan dalam parameter fungsi, maka ia berpotensi kurang selamat. Contohnya, bezakan safer() dengan update_ptrs_v2() di mana pemanggil mengawal penunjuk. Untuk pengetahuan semua anda, pemanggil mendapatnya salah dan memberikan petunjuk alias itu.
Hanya penunjuk kepada objek (atau batal) boleh layak dengan sekatan:
restrict int x; // error: can't restrict object int restrict *p; // error: pointer to restrict object int (*restrict f)(); // error: pointer-to-function
Anda boleh menggunakan restrict untuk ahli struct, contohnya:
struct node { void *restrict data; struct node *restrict left; struct node *restrict right; };
mengatakan bahawa data akan menjadi satu-satunya penunjuk kepada data itu dan kiri dan kanan itu tidak akan sekali-kali menghala ke nod yang sama. Walau bagaimanapun, penggunaan restrict untuk ahli struct adalah sangat jarang berlaku.
Akhir sekali, C++ tidak mempunyai sekatan. kenapa tidak Terdapat jawapan yang panjang, tetapi versi TL;DR ialah:
Walau bagaimanapun, banyak penyusun mempunyai __restrict__ sebagai sambungan.
Dalam kes terhad, penggunaan sekatan boleh membawa kepada peningkatan prestasi, tetapi terdapat beberapa masalah yang ketara. Jika anda mempertimbangkan untuk menggunakan sekatan, profilkan kod anda dahulu.
Gunakan dengan bijak.
Atas ialah kandungan terperinci Kata Kunci 'sekat' Kabur dalam C. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!