Saya baru saja melihat gambar hari ini dan berpikir saya akan menghargai penjelasan. Jadi inilah gambarnya:
Saya menemukan ini membingungkan dan bertanya-tanya apakah kode seperti itu pernah praktis. Saya mencari di Google gambar dan menemukan gambar lain di entri reddit ini , dan inilah gambar itu:
Jadi, "membaca spiral" ini adalah sesuatu yang valid? Apakah ini bagaimana kompiler C mengurai?
Akan lebih bagus jika ada penjelasan sederhana untuk kode aneh ini.
Terlepas dari semua itu, bisakah kode semacam ini berguna? Jika demikian, di mana dan kapan?
Ada pertanyaan tentang "aturan spiral", tapi saya tidak hanya bertanya tentang bagaimana penerapannya atau bagaimana ekspresi dibaca dengan aturan itu. Saya mempertanyakan penggunaan ekspresi seperti itu dan validitas aturan spiral juga. Mengenai ini, beberapa jawaban yang bagus sudah diposting.
f
sebagai array pointer ke fungsi yang dapat mengambil argumen .. jika yavoid (*(*f[])(void))(void);
, maka ya, itu akan menjadi fungsi yang tidak mengambil argumen ...Jawaban:
Ada aturan yang disebut "Searah Jarum Jam / Spiral" untuk membantu menemukan makna deklarasi yang kompleks.
Dari c-faq :
Anda dapat memeriksa tautan di atas untuk contoh.
Perhatikan juga bahwa untuk membantu Anda ada juga situs web bernama:
http://www.cdecl.org
Anda dapat memasukkan deklarasi C dan itu akan memberi arti bahasa Inggris. Untuk
itu output:
EDIT:
Seperti yang ditunjukkan dalam komentar oleh Random832 , aturan spiral tidak membahas array array dan akan menyebabkan hasil yang salah dalam (sebagian besar) deklarasi tersebut. Misalnya untuk
int **x[1][2];
aturan spiral mengabaikan fakta yang[]
memiliki prioritas lebih tinggi*
.Ketika di depan array array, pertama dapat menambahkan tanda kurung eksplisit sebelum menerapkan aturan spiral. Sebagai contoh:
int **x[1][2];
sama denganint **(x[1][2]);
(juga valid C) karena didahulukan dan aturan spiral kemudian membacanya dengan benar sebagai "x adalah array 1 dari array 2 dari pointer ke pointer ke int" yang merupakan deklarasi bahasa Inggris yang benar.Perhatikan bahwa masalah ini juga telah dibahas dalam jawaban ini oleh James Kanze (ditunjukkan oleh haccks di komentar).
sumber
Jenis "spiral" tidak sesuai dengan aturan yang didahulukan berikut ini:
Subskrip
[]
dan fungsi panggilan()
operator memiliki prioritas lebih tinggi daripada unary*
, jadi*f()
diuraikan sebagai*(f())
dan*a[]
diuraikan sebagai*(a[])
.Jadi jika Anda ingin pointer ke array atau pointer ke suatu fungsi, maka Anda perlu mengelompokkan secara eksplisit
*
dengan pengenal, seperti dalam(*a)[]
atau(*f)()
.Kemudian Anda menyadari itu
a
danf
bisa menjadi ekspresi yang lebih rumit dari sekadar pengidentifikasi; diT (*a)[N]
,a
bisa berupa pengidentifikasi sederhana, atau bisa berupa pemanggilan fungsi seperti(*f())[N]
(a
->f()
), atau bisa berupa array seperti(*p[M])[N]
, (a
->p[M]
), atau bisa berupa array pointer ke fungsi seperti(*(*p[M])())[N]
(a
->(*p[M])()
), dll.Akan lebih baik jika operator tipuan
*
adalah postfix bukannya unary, yang akan membuat deklarasi agak lebih mudah dibaca dari kiri ke kanan (void f[]*()*();
pasti mengalir lebih baik daripadavoid (*(*f[])())()
), tetapi ternyata tidak.Saat Anda menemukan deklarasi berbulu seperti itu, mulailah dengan menemukan pengidentifikasi paling kiri dan menerapkan aturan prioritas di atas, menerapkannya secara rekursif ke parameter fungsi apa pun:
The
signal
fungsi di perpustakaan standar mungkin spesimen tipe untuk jenis kegilaan:Pada titik ini kebanyakan orang mengatakan "use typedefs", yang tentunya merupakan opsi:
Tapi...
Bagaimana Anda akan menggunakan
f
ekspresi? Anda tahu itu adalah array pointer, tetapi bagaimana Anda menggunakannya untuk menjalankan fungsi yang benar? Anda harus memeriksa typedefs dan memecahkan sintaks yang benar. Sebaliknya, versi "telanjang" cukup menarik perhatian, tetapi ia memberi tahu Anda bagaimana tepatnya menggunakanf
ekspresi (yaitu(*(*f[i])())();
, dengan asumsi kedua fungsi tidak mengambil argumen).sumber
f
pohon perlambatan Anda , menjelaskan presedensi ... untuk beberapa alasan saya selalu mendapatkan tendangan ASCII-art, terutama ketika datang untuk menjelaskan hal-hal :)void
dalam tanda kurung fungsi, jika tidak dapat mengambil argumen apa pun.Dalam C, deklarasi mencerminkan penggunaan — itulah yang didefinisikan dalam standar. Deklarasi:
Merupakan pernyataan bahwa ekspresi
(*(*f[i])())()
menghasilkan tipe hasilvoid
. Yang berarti:f
harus berupa array, karena Anda dapat mengindeksnya:Unsur-unsur
f
harus menjadi petunjuk, karena Anda dapat merujuknya:Pointer tersebut harus menjadi pointer ke fungsi yang tidak menggunakan argumen, karena Anda dapat memanggilnya:
Hasil dari fungsi-fungsi itu juga harus berupa pointer, karena Anda dapat men-decereference mereka:
Pointer tersebut juga harus menjadi pointer ke fungsi yang tidak menggunakan argumen, karena Anda dapat memanggilnya:
Pointer fungsi tersebut harus dikembalikan
void
"Aturan spiral" hanyalah mnemonik yang menyediakan cara berbeda untuk memahami hal yang sama.
sumber
vector< function<function<void()>()>* > f
, terutama jika Anda menambahkan distd::
s. (Tapi yah, contohnya dibuat - buat ... bahkanf :: [IORef (IO (IO ()))]
terlihat aneh.)a[x]
menunjukkan bahwa ekspresia[i]
valid ketikai >= 0 && i < x
. Sedangkan,a[]
membiarkan ukuran tidak ditentukan, dan karenanya identik dengan*a
: itu menunjukkan bahwa ekspresia[i]
(atau yang setara*(a + i)
) valid untuk beberapa rentangi
.(*f[])()
adalah tipe yang dapat Anda indeks, lalu dereferensi, lalu panggil, jadi ini adalah array pointer ke fungsi.Menerapkan aturan spiral atau menggunakan cdecl tidak selalu valid. Keduanya gagal dalam beberapa kasus. Aturan spiral bekerja untuk banyak kasus, tetapi itu tidak universal .
Untuk menguraikan deklarasi kompleks, ingat dua aturan sederhana ini:
Selalu baca deklarasi dari dalam ke luar : Mulai dari yang paling dalam, jika ada, kurung. Temukan pengidentifikasi yang dideklarasikan, dan mulailah menguraikan deklarasi dari sana.
Ketika ada pilihan, selalu nikmat
[]
dan()
lebih*
: Jika*
mendahului pengidentifikasi dan[]
mengikutinya, pengidentifikasi mewakili array, bukan pointer. Demikian juga, jika*
mendahului pengidentifikasi dan()
mengikutinya, pengidentifikasi mewakili fungsi, bukan pointer. (Tanda kurung selalu dapat digunakan untuk mengesampingkan prioritas normal[]
dan()
lebih*
.)Aturan ini sebenarnya melibatkan zig-zag dari satu sisi pengidentifikasi ke sisi lain.
Sekarang menguraikan deklarasi sederhana
Menerapkan aturan:
Mari kita menguraikan deklarasi kompleks seperti
dengan menerapkan aturan di atas:
Berikut ini adalah GIF yang menunjukkan cara Anda (klik pada gambar untuk tampilan lebih besar):
Aturan yang disebutkan di sini diambil dari buku C Programming A Modern Approach oleh KN KING .
sumber
char (x())[5]
harus menghasilkan kesalahan sintaks tapi, cdecl mengurai sebagai: declarex
sebagai fungsi yang mengembalikan array yang 5 darichar
.Itu hanya "spiral" karena kebetulan ada, dalam deklarasi ini, hanya satu operator di setiap sisi dalam setiap tingkat tanda kurung. Mengklaim bahwa Anda melanjutkan "dalam spiral" umumnya akan menyarankan Anda bergantian antara array dan pointer dalam deklarasi
int ***foo[][][]
ketika pada kenyataannya semua level array datang sebelum salah satu level pointer.sumber
Saya ragu konstruksi seperti ini dapat digunakan di kehidupan nyata. Saya bahkan membenci mereka sebagai pertanyaan wawancara untuk pengembang reguler (mungkin OK untuk penulis kompiler). typedefs harus digunakan sebagai gantinya.
sumber
Sebagai fakta trivia acak, Anda mungkin merasa senang mengetahui bahwa ada kata aktual dalam bahasa Inggris untuk menggambarkan bagaimana deklarasi C dibaca: Boustrophedonically , yaitu, bergantian kanan-ke-kiri dengan kiri-ke-kanan.
Referensi: Van der Linden, 1994 - Halaman 76
sumber
Mengenai kegunaan dari ini, ketika bekerja dengan shellcode Anda melihat konstruksi ini banyak:
Meskipun tidak terlalu rumit secara sintaksis, pola khusus ini banyak muncul.
Contoh lebih lengkap dalam pertanyaan SO ini .
Jadi sementara kegunaan sejauh gambar aslinya dipertanyakan (saya akan menyarankan bahwa kode produksi harus disederhanakan secara drastis), ada beberapa konstruksi sintaksis yang muncul sedikit.
sumber
Deklarasi
hanyalah cara mengatakan yang tidak jelas
dengan
Dalam praktiknya, nama yang lebih deskriptif akan dibutuhkan alih-alih ResultFunction dan Function . Jika memungkinkan saya juga akan menentukan daftar parameter sebagai
void
.sumber
Saya menemukan metode yang dijelaskan oleh Bruce Eckel sangat membantu dan mudah diikuti:
Diambil dari: Berpikir dalam C ++ Volume 1, edisi kedua, bab 3, bagian "Function Addresses" oleh Bruce Eckel.
sumber
Kecuali dimodifikasi oleh tanda kurung, tentu saja. Dan perhatikan bahwa sintaks untuk mendeklarasikan ini persis mencerminkan sintaks untuk menggunakan variabel itu untuk mendapatkan turunan dari kelas dasar.
Serius, ini tidak sulit untuk dipelajari untuk dilakukan sekilas; Anda hanya harus bersedia meluangkan waktu untuk mempraktikkan keterampilan. Jika Anda ingin mempertahankan atau mengadaptasi kode C yang ditulis oleh orang lain, itu pasti layak untuk diinvestasikan saat itu. Ini juga merupakan trik pesta yang menyenangkan untuk menakuti programmer lain yang belum mempelajarinya.
Untuk kode Anda sendiri: seperti biasa, fakta bahwa sesuatu dapat ditulis sebagai satu-liner tidak berarti harus demikian, kecuali jika itu adalah pola yang sangat umum yang telah menjadi idiom standar (seperti loop-copy loop) . Anda, dan orang-orang yang mengikuti Anda, akan jauh lebih bahagia jika Anda membangun tipe-tipe rumit dari typedef berlapis dan dereference langkah-demi-langkah daripada mengandalkan kemampuan Anda untuk menghasilkan dan menguraikan ini "dengan satu gerakan membengkak." Kinerja akan sama baiknya, dan pembacaan kode dan pemeliharaan akan jauh lebih baik.
Bisa jadi lebih buruk, Anda tahu. Ada pernyataan PL / I legal yang dimulai dengan sesuatu seperti:
sumber
IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIF
dan diuraikan sebagaiif (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF)
.Saya kebetulan adalah penulis asli dari aturan spiral yang saya tulis oh bertahun-tahun yang lalu (ketika saya memiliki banyak rambut :) dan merasa terhormat ketika ditambahkan ke cfaq.
Saya menulis aturan spiral sebagai cara untuk memudahkan siswa dan kolega saya untuk membaca deklarasi C "di kepala mereka"; yaitu, tanpa harus menggunakan alat perangkat lunak seperti cdecl.org, dll. Tidak pernah maksud saya untuk menyatakan bahwa aturan spiral menjadi cara kanonik untuk mengurai ekspresi C. Namun saya senang melihat peraturan ini telah membantu ribuan siswa dan praktisi pemrograman C selama bertahun-tahun!
Untuk catatan,
Sudah "benar" teridentifikasi beberapa kali di banyak situs, termasuk oleh Linus Torvalds (seseorang yang sangat saya hormati), bahwa ada situasi di mana aturan spiral saya "rusak". Yang paling umum:
Seperti yang ditunjukkan oleh orang lain di utas ini, aturan dapat diperbarui untuk mengatakan bahwa ketika Anda menemukan array, cukup konsumsi semua indeks seolah-olah ditulis seperti:
Sekarang, mengikuti aturan spiral, saya akan mendapatkan:
"ar adalah array pointer dua dimensi 10x10 untuk karakter"
Saya berharap aturan spiral membawa manfaatnya dalam belajar C!
PS:
Saya suka gambar "C tidak sulit" :)
sumber
(*(*f[]) ()) ()
Menyelesaikan
void
>>(*(*f[]) ())
() = batalResoiving
()
>>(*f[]) ()
) = mengembalikan fungsi (batal)Menyelesaikan
*
>>(*f[])
() = penunjuk ke (fungsi kembali (kosong))Menyelesaikan
()
>>f[]
) = fungsi kembali (penunjuk ke (fungsi kembali (kosong)))Menyelesaikan
*
>>f
[] = penunjuk ke (fungsi kembali (penunjuk ke (fungsi kembali (kosong)))))Menyelesaikan
[ ]
>>sumber