C pointer ke deklarasi array dengan bitwise dan operator

9

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:

  1. iscntrl ('a') dipanggil dan 'a' dikonversi ke nilai integernya
  2. periksa dulu apakah _c adalah -1 lalu kembalikan 0 lagi ...
  3. menambah alamat titik penunjuk yang tidak ditentukan sebesar 1
  4. menyatakan alamat ini sebagai penunjuk ke array panjang (unsigned char) ((int) 'a')
  5. 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.

accentWool
sumber
1
_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. Sepertinya 0x20bit 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 bekerja EOFbahkan tanpa tes tambahan.)
Steve Summit
Para pemain (unsigned char)adalah untuk menjaga kemungkinan karakter ditandatangani dan negatif.
Steve Summit

Jawaban:

3

_ctype_tampaknya menjadi versi internal terbatas dari tabel simbol dan saya menduga + 1ini adalah bahwa mereka tidak repot-repot menyimpan indeks 0sejak 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:

Dalam semua kasus, argumennya adalah int, nilai yang harus dinyatakan sebagai unsigned charatau harus sama dengan nilai makroEOF

Melewati kode langkah demi langkah:

  • int iscntrl(int _c)The intjenis benar-benar karakter, tapi semua fungsi ctype.h diminta untuk menangani EOF, sehingga mereka harus int.
  • Cek terhadap -1adalah cek terhadap EOF, 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 sebagai unsigned char. Perhatikan bahwa charsebenarnya dapat memiliki nilai negatif, jadi ini adalah pemrograman defensif. Hasil dari []akses array adalah satu karakter dari tabel simbol internal mereka.
  • The &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.
  • Apa pun dengan bit 5 yang ditetapkan akan mengembalikan nilai yang ditutupi dengan 0x20, yang merupakan nilai bukan nol. Ini menyatakan persyaratan fungsi yang mengembalikan non-nol jika boolean benar.
Lundin
sumber
Tidak benar bahwa para pemeran menyatakan persyaratan standar bahwa nilainya dapat dinyatakan sebagai unsigned char. Standar ini mensyaratkan bahwa nilai sudah * dapat direpresentasikan sebagai unsigned char, atau sama EOF, ketika rutin dipanggil. Para pemain hanya berfungsi sebagai pemrograman "defensif": Memperbaiki kesalahan seorang programmer yang melewati tanda tangan char(atau a signed char) ketika tanggung jawab ada pada mereka untuk memberikan unsigned charnilai saat menggunakan ctype.hmakro. Perlu dicatat bahwa ini tidak dapat memperbaiki kesalahan ketika charnilai −1 dilewatkan dalam implementasi yang menggunakan −1 untuk EOF.
Eric Postpischil
Ini juga menawarkan penjelasan tentang + 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, EOFnilai −1 tidak akan bekerja dengan pemain itu, jadi mereka menambahkan operator bersyarat untuk memperlakukannya secara khusus.
Eric Postpischil
3

_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 karakter c. Ini adalah hal yang sama dengan mengatakan bahwa _ctype_ + 1menunjuk ke array 256 karakter di mana (_ctype_ + 1)[c]mewakili kategorisasi karakter c.

(_ctype_ + 1)[(unsigned char)_c]bukan deklarasi. Ini adalah ekspresi menggunakan operator subscript array. Ini mengakses posisi (unsigned char)_carray yang dimulai (_ctype_ + 1).

Kode yang dilempar _cdari intke unsigned chartidak sepenuhnya diperlukan: fungsi ctype mengambil nilai char cast ke unsigned char( charditandatangani di OpenBSD): panggilan yang benar adalah char c; … iscntrl((unsigned char)c). Mereka memiliki keuntungan menjamin bahwa tidak ada buffer overflow: jika aplikasi memanggil iscntrldengan nilai yang di luar kisaran unsigned chardan 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 disebut char c; … iscntrl(c)asalkan ctidak -1.

Alasan untuk kasus khusus dengan -1 adalah karena itu EOF. Banyak fungsi C standar yang beroperasi pada char, misalnya getchar, mewakili karakter sebagai intnilai yang merupakan nilai karakter yang dibungkus ke rentang positif, dan menggunakan nilai khusus EOF == -1untuk menunjukkan bahwa tidak ada karakter yang dapat dibaca. Untuk fungsi seperti getchar, EOFmenunjukkan akhir dari file, maka nama e nd- o f- f ile. Eric Postpischil menyarankan bahwa kode itu awalnya adil return _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 vnilai ditemukan dalam array, v & _Ctes apakah bit at 0x20diatur dalam v. Nilai-nilai dalam array adalah topeng dari kategori di mana karakter berada: _Cdiatur untuk karakter kontrol, _Udiatur untuk huruf besar, dll.

Gilles 'SANGAT berhenti menjadi jahat'
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 satu EOFatau unsigned charnilai. 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 sebagai EOF.
Eric Postpischil
Ini juga menawarkan penjelasan tentang + 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, EOFnilai −1 tidak akan bekerja dengan pemain itu, jadi mereka menambahkan operator bersyarat untuk memperlakukannya secara khusus.
Eric Postpischil
2

Saya akan mulai dengan langkah 3:

menambah alamat titik penunjuk yang tidak ditentukan sebesar 1

Pointer tidak terdefinisi. Itu baru didefinisikan di beberapa unit kompilasi lainnya. Itulah externbagian 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.

4386427
sumber
1

Semua informasi di sini didasarkan pada analisis kode sumber (dan pengalaman pemrograman).

Deklarasi

extern const char *_ctype_;

memberitahu kompiler bahwa ada pointer ke const charsuatu tempat bernama _ctype_.

(4) Pointer ini diakses sebagai array.

(_ctype_ + 1)[(unsigned char)_c]

Para pemeran (unsigned char)_cmemastikan nilai indeks berada dalam kisaran unsigned char(0..255).

Aritmatika pointer _ctype_ + 1secara 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 karakter 0.. 255meninggalkan 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.

Bodo
sumber
Saya kira mereka memindahkan + 1ke pointer untuk memperjelas bahwa mereka mengakses elemen, 1..256bukan 1..255,0. _ctype_[1 + (unsigned char)_c]akan menjadi setara karena konversi implisit ke int. Dan _ctype_[(_c & 0xff) + 1]akan lebih jelas dan ringkas.
cmaster - mengembalikan monica
0

Kuncinya di sini adalah untuk memahami apa yang dilakukan ekspresi (_ctype_ + 1)[(unsigned char)_c](yang kemudian diumpankan ke bitwise dan operasi, & 0x20untuk mendapatkan hasilnya!

Jawaban singkat: Ini mengembalikan elemen _c + 1array 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:

#include <stdio.h>
int main() {
    // Code like the following two lines will be defined somewhere in the run-time
    // libraries with which your program is linked, only using _ctype_ in place of _qlist_ ...
    const char list[] = "abcdefghijklmnopqrstuvwxyz";
    const char* _qlist_ = list;
    // These two lines show how expressions like (a)[b] and (a+1)[b] just boil down to
    // a[b] and a[b+1], respectively ...
    char p = (_qlist_)[6];
    char q = (_qlist_ + 1)[6];
    printf("p = %c  q = %c\n", p, q);
    return 0;
}

Jangan ragu untuk meminta klarifikasi dan / atau penjelasan lebih lanjut.

Adrian Mole
sumber
0

Fungsi yang dideklarasikan dalam ctype.hmenerima objek dari tipe int. Untuk karakter yang digunakan sebagai argumen, diasumsikan bahwa karakter tersebut merupakan awal dari tipe unsigned char. Karakter ini digunakan sebagai indeks dalam tabel yang menentukan karakteristik karakter.

Tampaknya cek _c == -1tersebut digunakan dalam kasus ketika _cberisi nilai EOF. Jika tidak EOFmaka _c dilemparkan ke tipe unsigned char yang digunakan sebagai indeks dalam tabel yang ditunjukkan oleh ekspresi _ctype_ + 1. Dan jika bit yang ditentukan oleh mask 0x20diatur maka karakternya adalah simbol kontrol.

Untuk memahami ungkapan

(_ctype_ + 1)[(unsigned char)_c]

memperhitungkan bahwa array subscript adalah operator postfix yang didefinisikan seperti

postfix-expression [ expression ]

Anda mungkin tidak menulis suka

_ctype_ + 1[(unsigned char)_c]

karena ungkapan ini setara dengan

_ctype_ + ( 1[(unsigned char)_c] )

Jadi ekspresi _ctype_ + 1tertutup dalam tanda kurung untuk mendapatkan ekspresi primer.

Jadi sebenarnya sudah

pointer[integral_expression]

yang menghasilkan objek array pada indeks yang dihitung sebagai ekspresi di integral_expressionmana pointer (_ctype_ + 1)(gere digunakan pointer arithmetuc) dan integral_expressionitu adalah indeks adalah ekspresi (unsigned char)_c.

Vlad dari Moskow
sumber