Saya ingin memahami kode berikut:
//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}
Itu berasal dari file ctype.h dari kode sumber sistem operasi obenbsd. Fungsi ini memeriksa apakah char adalah karakter kontrol atau huruf yang dapat dicetak di dalam rentang ascii. Inilah rantai pemikiran saya saat ini:
- iscntrl ('a') dipanggil dan 'a' dikonversi ke nilai integernya
- periksa dulu apakah _c adalah -1 lalu kembalikan 0 lagi ...
- menambah alamat titik penunjuk yang tidak ditentukan sebesar 1
- menyatakan alamat ini sebagai penunjuk ke array panjang (unsigned char) ((int) 'a')
- terapkan bitwise dan operator ke _C (0x20) dan array (???)
Entah bagaimana, anehnya, ia berfungsi dan setiap kali 0 dikembalikan, karakter yang diberikan bukanlah karakter yang dapat dicetak. Kalau tidak, ketika itu dapat dicetak fungsi hanya mengembalikan nilai integer yang tidak menarik. Masalah pemahaman saya ada di langkah 3, 4 (sedikit) dan 5.
Terima kasih atas bantuannya.
_ctype_
pada dasarnya adalah array dari bitmask. Itu diindeks oleh karakter yang menarik. Jadi_ctype_['A']
akan mengandung bit yang sesuai dengan "alpha" dan "huruf besar",_ctype_['a']
akan mengandung bit yang sesuai dengan "alpha" dan "huruf kecil",_ctype_['1']
akan mengandung bit yang sesuai dengan "digit", dll. Sepertinya0x20
bit yang sesuai dengan "control" . Tapi untuk beberapa alasan_ctype_
array diimbangi dengan 1, sehingga bit untuk'a'
benar-benar dalam_ctype_['a'+1]
. (Itu mungkin membuatnya bekerjaEOF
bahkan tanpa tes tambahan.)(unsigned char)
adalah untuk menjaga kemungkinan karakter ditandatangani dan negatif.Jawaban:
_ctype_
tampaknya menjadi versi internal terbatas dari tabel simbol dan saya menduga+ 1
ini adalah bahwa mereka tidak repot-repot menyimpan indeks0
sejak itu tidak dapat dicetak. Atau mungkin mereka menggunakan tabel 1-diindeks daripada 0-diindeks seperti kebiasaan di C.Standar C menentukan ini untuk semua fungsi ctype.h:
Melewati kode langkah demi langkah:
int iscntrl(int _c)
Theint
jenis benar-benar karakter, tapi semua fungsi ctype.h diminta untuk menanganiEOF
, sehingga mereka harusint
.-1
adalah cek terhadapEOF
, karena memiliki nilai-1
._ctype+1
adalah pointer aritmatika untuk mendapatkan alamat item array.[(unsigned char)_c]
hanyalah sebuah akses array dari array itu, di mana para pemain ada untuk menegakkan persyaratan standar dari parameter yang diwakili sebagaiunsigned char
. Perhatikan bahwachar
sebenarnya dapat memiliki nilai negatif, jadi ini adalah pemrograman defensif. Hasil dari[]
akses array adalah satu karakter dari tabel simbol internal mereka.&
masking ada untuk mendapatkan sekelompok karakter tertentu dari tabel simbol. Rupanya semua karakter dengan bit 5 set (mask 0x20) adalah karakter kontrol. Tidak ada artinya ini tanpa melihat tabel.sumber
unsigned char
. Standar ini mensyaratkan bahwa nilai sudah * dapat direpresentasikan sebagaiunsigned char
, atau samaEOF
, ketika rutin dipanggil. Para pemain hanya berfungsi sebagai pemrograman "defensif": Memperbaiki kesalahan seorang programmer yang melewati tanda tanganchar
(atau asigned char
) ketika tanggung jawab ada pada mereka untuk memberikanunsigned char
nilai saat menggunakanctype.h
makro. Perlu dicatat bahwa ini tidak dapat memperbaiki kesalahan ketikachar
nilai −1 dilewatkan dalam implementasi yang menggunakan −1 untukEOF
.+ 1
. Jika makro sebelumnya tidak mengandung penyesuaian defensif ini, maka itu bisa diimplementasikan hanya sebagai((_ctype_+1)[_c] & _C)
, sehingga memiliki tabel diindeks dengan nilai-nilai pra-penyesuaian −1 hingga 255. Jadi entri pertama tidak dilewati dan memang melayani tujuan. Ketika seseorang kemudian menambahkan pemain bertahan,EOF
nilai −1 tidak akan bekerja dengan pemain itu, jadi mereka menambahkan operator bersyarat untuk memperlakukannya secara khusus._ctype_
adalah pointer ke array global 257 byte. Saya tidak tahu untuk apa_ctype_[0]
._ctype_[1]
melalui_ctype_[256]_
mewakili kategori karakter masing-masing karakter 0,…, 255:_ctype_[c + 1]
mewakili kategori karakterc
. Ini adalah hal yang sama dengan mengatakan bahwa_ctype_ + 1
menunjuk ke array 256 karakter di mana(_ctype_ + 1)[c]
mewakili kategorisasi karakterc
.(_ctype_ + 1)[(unsigned char)_c]
bukan deklarasi. Ini adalah ekspresi menggunakan operator subscript array. Ini mengakses posisi(unsigned char)_c
array yang dimulai(_ctype_ + 1)
.Kode yang dilempar
_c
dariint
keunsigned char
tidak sepenuhnya diperlukan: fungsi ctype mengambil nilai char cast keunsigned char
(char
ditandatangani di OpenBSD): panggilan yang benar adalahchar c; … iscntrl((unsigned char)c)
. Mereka memiliki keuntungan menjamin bahwa tidak ada buffer overflow: jika aplikasi memanggiliscntrl
dengan nilai yang di luar kisaranunsigned char
dan bukan -1, fungsi ini mengembalikan nilai yang mungkin tidak bermakna tetapi setidaknya tidak akan menyebabkan crash atau kebocoran data pribadi yang terjadi pada alamat di luar batas array. Nilainya bahkan benar jika fungsinya disebutchar c; … iscntrl(c)
asalkanc
tidak -1.Alasan untuk kasus khusus dengan -1 adalah karena itu
EOF
. Banyak fungsi C standar yang beroperasi padachar
, misalnyagetchar
, mewakili karakter sebagaiint
nilai yang merupakan nilai karakter yang dibungkus ke rentang positif, dan menggunakan nilai khususEOF == -1
untuk menunjukkan bahwa tidak ada karakter yang dapat dibaca. Untuk fungsi sepertigetchar
,EOF
menunjukkan akhir dari file, maka nama e nd- o f- f ile. Eric Postpischil menyarankan bahwa kode itu awalnya adilreturn _ctype_[_c + 1]
, dan itu mungkin benar:_ctype_[0]
akan menjadi nilai untuk EOF. Implementasi yang lebih sederhana ini menghasilkan buffer overflow jika fungsinya disalahgunakan, sedangkan implementasi saat ini menghindari hal ini seperti dibahas di atas.Jika
v
nilai ditemukan dalam array,v & _C
tes apakah bit at0x20
diatur dalamv
. Nilai-nilai dalam array adalah topeng dari kategori di mana karakter berada:_C
diatur untuk karakter kontrol,_U
diatur untuk huruf besar, dll.sumber
(_ctype_ + 1)[_c]
akan menggunakan indeks array yang benar seperti yang ditentukan oleh standar C, karena itu adalah tanggung jawab pengguna untuk melewati salah satuEOF
atauunsigned char
nilai. Perilaku untuk nilai-nilai lain tidak ditentukan oleh standar C. Para pemain tidak melayani untuk menerapkan perilaku yang diperlukan oleh standar C. Ini adalah solusi untuk melindungi bug yang disebabkan oleh programmer yang secara salah melewati nilai karakter negatif. Namun, itu tidak lengkap atau salah (dan tidak dapat diperbaiki) karena nilai karakter −1 tentu akan diperlakukan sebagaiEOF
.+ 1
. Jika makro sebelumnya tidak mengandung penyesuaian defensif ini, maka itu bisa diimplementasikan hanya sebagai((_ctype_+1)[_c] & _C)
, sehingga memiliki tabel diindeks dengan nilai-nilai pra-penyesuaian −1 hingga 255. Jadi entri pertama tidak dilewati dan memang melayani tujuan. Ketika seseorang kemudian menambahkan pemain bertahan,EOF
nilai −1 tidak akan bekerja dengan pemain itu, jadi mereka menambahkan operator bersyarat untuk memperlakukannya secara khusus.Saya akan mulai dengan langkah 3:
Pointer tidak terdefinisi. Itu baru didefinisikan di beberapa unit kompilasi lainnya. Itulah
extern
bagian yang memberitahu kompiler. Jadi ketika semua file dihubungkan bersama, linker akan menyelesaikan referensi untuk itu.Jadi apa artinya ini?
Itu menunjuk ke array dengan informasi tentang masing-masing karakter. Setiap karakter memiliki entri sendiri. Entri adalah representasi bitmap karakteristik untuk karakter. Sebagai contoh: Jika bit 5 diatur, itu berarti karakter tersebut adalah karakter kontrol. Contoh lain: Jika bit 0 diatur, itu berarti karakter tersebut adalah karakter atas.
Jadi sesuatu seperti
(_ctype_ + 1)['x']
akan mendapatkan karakteristik yang berlaku'x'
. Kemudian bitwise dan dilakukan untuk memeriksa apakah bit 5 diatur, yaitu memeriksa apakah itu adalah karakter kontrol.Alasan untuk menambahkan 1 mungkin karena indeks nyata 0 dicadangkan untuk beberapa tujuan khusus.
sumber
Semua informasi di sini didasarkan pada analisis kode sumber (dan pengalaman pemrograman).
Deklarasi
memberitahu kompiler bahwa ada pointer ke
const char
suatu tempat bernama_ctype_
.(4) Pointer ini diakses sebagai array.
Para pemeran
(unsigned char)_c
memastikan nilai indeks berada dalam kisaranunsigned char
(0..255).Aritmatika pointer
_ctype_ + 1
secara efektif menggeser posisi array sebanyak 1 elemen. Saya tidak tahu mengapa mereka mengimplementasikan array dengan cara ini. Menggunakan rentang_ctype_[1]
.._ctype[256]
untuk nilai karakter0
..255
meninggalkan nilai yang_ctype_[0]
tidak digunakan untuk fungsi ini. (Offset 1 dapat diimplementasikan dengan beberapa cara alternatif.)Akses array mengambil nilai (tipe
char
, untuk menghemat ruang) menggunakan nilai karakter sebagai indeks array.(5) Operasi bitwise AND mengekstrak bit tunggal dari nilai.
Rupanya nilai dari array digunakan sebagai bidang bit di mana bit 5 (dihitung dari 0 mulai bit paling signifikan, =
0x20
) adalah bendera untuk "adalah karakter kontrol". Jadi array berisi nilai-nilai bidang bit yang menggambarkan properti karakter.sumber
+ 1
ke pointer untuk memperjelas bahwa mereka mengakses elemen,1..256
bukan1..255,0
._ctype_[1 + (unsigned char)_c]
akan menjadi setara karena konversi implisit keint
. Dan_ctype_[(_c & 0xff) + 1]
akan lebih jelas dan ringkas.Kuncinya di sini adalah untuk memahami apa yang dilakukan ekspresi
(_ctype_ + 1)[(unsigned char)_c]
(yang kemudian diumpankan ke bitwise dan operasi,& 0x20
untuk mendapatkan hasilnya!Jawaban singkat: Ini mengembalikan elemen
_c + 1
array yang ditunjuk oleh_ctype_
.Bagaimana?
Pertama, meskipun Anda tampaknya berpikir
_ctype_
tidak terdefinisi , sebenarnya tidak! Header menyatakannya sebagai variabel eksternal - tetapi didefinisikan dalam (hampir pasti) salah satu pustaka run-time yang terkait dengan program Anda ketika Anda membangunnya.Untuk mengilustrasikan bagaimana sintaksis berhubungan dengan pengindeksan array, coba kerjakan (bahkan kompilasi) program singkat berikut:
Jangan ragu untuk meminta klarifikasi dan / atau penjelasan lebih lanjut.
sumber
Fungsi yang dideklarasikan dalam
ctype.h
menerima objek dari tipeint
. Untuk karakter yang digunakan sebagai argumen, diasumsikan bahwa karakter tersebut merupakan awal dari tipeunsigned char
. Karakter ini digunakan sebagai indeks dalam tabel yang menentukan karakteristik karakter.Tampaknya cek
_c == -1
tersebut digunakan dalam kasus ketika_c
berisi nilaiEOF
. Jika tidakEOF
maka _c dilemparkan ke tipe unsigned char yang digunakan sebagai indeks dalam tabel yang ditunjukkan oleh ekspresi_ctype_ + 1
. Dan jika bit yang ditentukan oleh mask0x20
diatur maka karakternya adalah simbol kontrol.Untuk memahami ungkapan
memperhitungkan bahwa array subscript adalah operator postfix yang didefinisikan seperti
Anda mungkin tidak menulis suka
karena ungkapan ini setara dengan
Jadi ekspresi
_ctype_ + 1
tertutup dalam tanda kurung untuk mendapatkan ekspresi primer.Jadi sebenarnya sudah
yang menghasilkan objek array pada indeks yang dihitung sebagai ekspresi di
integral_expression
mana pointer(_ctype_ + 1)
(gere digunakan pointer arithmetuc) danintegral_expression
itu adalah indeks adalah ekspresi(unsigned char)_c
.sumber