C tidak terlalu sulit: void (* (* f []) ()) ()

188

Saya baru saja melihat gambar hari ini dan berpikir saya akan menghargai penjelasan. Jadi inilah gambarnya:

beberapa kode c

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:

beberapa penjelasan menarik

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.

Motun
sumber
9
Bagaimana penjelasannya bisa lebih sederhana? Ini mencakup semua aspek dari definisi beberapa kata untuk setiap poin kunci.
Scott Hunter
30
Mungkin C itu sulit? Itu benar-benar menyatakan fsebagai array pointer ke fungsi yang dapat mengambil argumen .. jika ya void (*(*f[])(void))(void);, maka ya, itu akan menjadi fungsi yang tidak mengambil argumen ...
txtechhelp
18
Dalam praktiknya, jangan kode kode yang tidak jelas tersebut. Gunakan typedef untuk tanda tangan
Basile Starynkevitch
4
setiap deklarasi yang melibatkan pointer fungsi bisa sulit. Itu tidak berarti bahwa C atau C ++ normal sulit dengan cara yang sama. Bahasa lain menyelesaikan ini dengan cara yang berbeda termasuk tidak memiliki fungsi pointer, yang dapat menjadi penghilangan yang signifikan dalam beberapa kasus
Kate Gregory
20
Jika Anda menyipitkan mata, sepertinya LISP.
user2023861

Jawaban:

117

Ada aturan yang disebut "Searah Jarum Jam / Spiral" untuk membantu menemukan makna deklarasi yang kompleks.

Dari c-faq :

Ada tiga langkah sederhana untuk diikuti:

  1. Dimulai dengan elemen yang tidak diketahui, bergerak dalam arah spiral / searah jarum jam; ketika menemukan elemen-elemen berikut menggantinya dengan pernyataan bahasa Inggris yang sesuai:

    [X]atau []
    => Array X ukuran ... atau Array ukuran tidak ditentukan ...

    (type1, type2)
    => fungsi lewat type1 dan type2 kembali ...

    *
    => pointer ke ...

  2. Terus lakukan ini dalam arah spiral / searah jarum jam sampai semua token telah ditutup.

  3. Selalu selesaikan apa pun di dalam tanda kurung terlebih dahulu!

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

void (*(*f[])())()

itu output:

menyatakan f sebagai array pointer berfungsi mengembalikan pointer ke fungsi mengembalikan batal

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 dengan int **(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).

ouah
sumber
5
Saya berharap cdecl.org lebih baik
Grady Player
8
Tidak ada "aturan spiral" ... "int *** foo [] [] []" mendefinisikan array array array pointer ke pointer ke pointer. "Spiral" hanya datang dari fakta bahwa deklarasi ini terjadi pada kelompok hal-hal dalam tanda kurung dengan cara yang menyebabkan mereka berganti. Semuanya ada di kanan, lalu ke kiri, di dalam setiap tanda kurung.
Acak832
1
@ Random832 Ada "aturan spiral", dan itu mencakup kasus yang baru saja Anda sebutkan, yaitu berbicara tentang bagaimana berurusan dengan tanda kurung / array dll. Tentu saja bukan aturan Standar C, tetapi mnemonik yang baik untuk mencari tahu bagaimana menangani dengan deklarasi yang rumit. IMHO, ini sangat berguna dan menyelamatkan Anda saat dalam kesulitan atau ketika cdecl.org tidak dapat menguraikan deklarasi. Tentu saja seseorang seharusnya tidak menyalahgunakan deklarasi semacam itu, tetapi ada baiknya untuk mengetahui bagaimana mereka diuraikan.
vsoftco
5
@vsoftco Tapi itu bukan "bergerak dalam arah spiral / searah jarum jam" jika Anda hanya berbalik ketika Anda mencapai tanda kurung.
Acak832
2
ouah, Anda harus menyebutkan bahwa aturan spiral tidak universal .
pukul
105

Jenis "spiral" tidak sesuai dengan aturan yang didahulukan berikut ini:

T *a[]    -- a is an array of pointer to T
T (*a)[]  -- a is a pointer to an array of T
T *f()    -- f is a function returning a pointer to T
T (*f)()  -- f is a pointer to a function returning T

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 adan fbisa menjadi ekspresi yang lebih rumit dari sekadar pengidentifikasi; di T (*a)[N], abisa 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 daripada void (*(*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:

         f              -- f
         f[]            -- is an array
        *f[]            -- of pointers  ([] has higher precedence than *)
       (*f[])()         -- to functions
      *(*f[])()         -- returning pointers
     (*(*f[])())()      -- to functions
void (*(*f[])())();     -- returning void

The signalfungsi di perpustakaan standar mungkin spesimen tipe untuk jenis kegilaan:

       signal                                       -- signal
       signal(                          )           -- is a function with parameters
       signal(    sig,                  )           --    sig
       signal(int sig,                  )           --    which is an int and
       signal(int sig,        func      )           --    func
       signal(int sig,       *func      )           --    which is a pointer
       signal(int sig,      (*func)(int))           --    to a function taking an int                                           
       signal(int sig, void (*func)(int))           --    returning void
      *signal(int sig, void (*func)(int))           -- returning a pointer
     (*signal(int sig, void (*func)(int)))(int)     -- to a function taking an int
void (*signal(int sig, void (*func)(int)))(int);    -- and returning void

Pada titik ini kebanyakan orang mengatakan "use typedefs", yang tentunya merupakan opsi:

typedef void outerfunc(void);
typedef outerfunc *innerfunc(void);

innerfunc *f[N];

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 menggunakan f ekspresi (yaitu (*(*f[i])())();, dengan asumsi kedua fungsi tidak mengambil argumen).

John Bode
sumber
7
Terima kasih telah memberikan contoh 'sinyal', menunjukkan bahwa hal-hal semacam ini memang muncul di alam liar.
Justsalt
Itu adalah contoh yang bagus.
Casey
Saya menyukai fpohon perlambatan Anda , menjelaskan presedensi ... untuk beberapa alasan saya selalu mendapatkan tendangan ASCII-art, terutama ketika datang untuk menjelaskan hal-hal :)
txtechhelp
1
dengan asumsi tidak ada fungsi yang mengambil argumen : maka Anda harus menggunakan voiddalam tanda kurung fungsi, jika tidak dapat mengambil argumen apa pun.
pukul
1
@haccks: untuk deklarasi, ya; Saya sedang berbicara tentang pemanggilan fungsi.
John Bode
57

Dalam C, deklarasi mencerminkan penggunaan — itulah yang didefinisikan dalam standar. Deklarasi:

void (*(*f[])())()

Merupakan pernyataan bahwa ekspresi (*(*f[i])())()menghasilkan tipe hasil void. Yang berarti:

  • f harus berupa array, karena Anda dapat mengindeksnya:

    f[i]
  • Unsur-unsur fharus menjadi petunjuk, karena Anda dapat merujuknya:

    *f[i]
  • Pointer tersebut harus menjadi pointer ke fungsi yang tidak menggunakan argumen, karena Anda dapat memanggilnya:

    (*f[i])()
  • Hasil dari fungsi-fungsi itu juga harus berupa pointer, karena Anda dapat men-decereference mereka:

    *(*f[i])()
  • Pointer tersebut juga harus menjadi pointer ke fungsi yang tidak menggunakan argumen, karena Anda dapat memanggilnya:

    (*(*f[i])())()
  • Pointer fungsi tersebut harus dikembalikan void

"Aturan spiral" hanyalah mnemonik yang menyediakan cara berbeda untuk memahami hal yang sama.

Jon Purdy
sumber
3
Cara hebat untuk melihatnya yang belum pernah saya lihat sebelumnya. +1
tbodt
4
Bagus. Dilihat dengan cara ini, sangat sederhana . Sebenarnya lebih mudah daripada sesuatu seperti vector< function<function<void()>()>* > f, terutama jika Anda menambahkan di std::s. (Tapi yah, contohnya dibuat - buat ... bahkan f :: [IORef (IO (IO ()))]terlihat aneh.)
leftaroundtentang
1
@TimoDenk: Deklarasi a[x]menunjukkan bahwa ekspresi a[i]valid ketika i >= 0 && i < x. Sedangkan, a[]membiarkan ukuran tidak ditentukan, dan karenanya identik dengan *a: itu menunjukkan bahwa ekspresi a[i](atau yang setara *(a + i)) valid untuk beberapa rentang i.
Jon Purdy
4
Sejauh ini, ini adalah cara termudah untuk memikirkan tipe C, terima kasih untuk ini
Alex Ozer
4
Aku suka ini! Jauh lebih mudah untuk berpikir daripada spiral yang konyol. (*f[])()adalah tipe yang dapat Anda indeks, lalu dereferensi, lalu panggil, jadi ini adalah array pointer ke fungsi.
Lynn
32

Jadi, "membaca spiral" ini adalah sesuatu yang valid?

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

int *a[10];

Menerapkan aturan:

int *a[10];      "a is"  
     ^  

int *a[10];      "a is an array"  
      ^^^^ 

int *a[10];      "a is an array of pointers"
    ^

int *a[10];      "a is an array of pointers to `int`".  
^^^      

Mari kita menguraikan deklarasi kompleks seperti

void ( *(*f[]) () ) ();  

dengan menerapkan aturan di atas:

void ( *(*f[]) () ) ();        "f is"  
          ^  

void ( *(*f[]) () ) ();        "f is an array"  
           ^^ 

void ( *(*f[]) () ) ();        "f is an array of pointers" 
         ^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function"   
               ^^     

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer"
       ^   

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function" 
                    ^^    

void ( *(*f[]) () ) ();        "f is an array of pointers to function returning pointer to function returning `void`"  
^^^^

Berikut ini adalah GIF yang menunjukkan cara Anda (klik pada gambar untuk tampilan lebih besar):

masukkan deskripsi gambar di sini


Aturan yang disebutkan di sini diambil dari buku C Programming A Modern Approach oleh KN KING .

haccks
sumber
Ini seperti pendekatan standar yaitu "deklarasi mencerminkan penggunaan". Saya ingin menanyakan hal lain pada saat ini: Apakah Anda menyarankan buku KN King? Saya melihat banyak ulasan bagus tentang buku itu.
Motun
1
Ya. Saya menyarankan buku itu. Saya mulai pemrograman dari buku itu. Teks dan masalah bagus di sana.
pukul
Bisakah Anda memberikan contoh cdecl gagal memahami deklarasi? Saya pikir cdecl menggunakan aturan parsing yang sama dengan kompiler, dan sejauh yang saya tahu itu selalu berhasil.
Fabio berkata Reinstate Monica
@FabioTurati; Suatu fungsi tidak dapat mengembalikan array atau fungsi. char (x())[5]harus menghasilkan kesalahan sintaks tapi, cdecl mengurai sebagai: declare xsebagai fungsi yang mengembalikan array yang 5 darichar .
pukul
12

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.

Random832
sumber
Nah, dalam "pendekatan spiral", Anda bergerak ke kanan sejauh yang Anda bisa, lalu ke kiri sejauh yang Anda bisa, dll. Tapi itu sering dijelaskan dengan keliru ...
Lynn
7

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.

SergeyA
sumber
3
Meskipun demikian, penting untuk mengetahui cara menguraikannya, meskipun hanya untuk mengetahui cara menguraikan typedef!
inetknght
1
@inetknght, cara Anda melakukannya dengan typedef adalah membuatnya cukup sederhana sehingga tidak perlu parsing.
SergeyA
2
Orang-orang yang mengajukan pertanyaan-pertanyaan semacam ini selama wawancara hanya melakukannya untuk membelai ego mereka.
Casey
1
@JohnBode, dan Anda akan membantu diri Anda dengan mengetikkan nilai balik fungsi.
SergeyA
1
@ JohnBode, saya merasa ini masalah pilihan pribadi yang tidak layak diperdebatkan. Saya melihat preferensi Anda, saya masih punya milik saya.
SergeyA
7

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

asamarin
sumber
1
Kata itu tidak menunjukkan di dalam seperti yang diulangi oleh paren atau pada satu baris. Ini menggambarkan pola "ular", dengan garis LTR diikuti oleh garis RTL.
Potatoswatter
5

Mengenai kegunaan dari ini, ketika bekerja dengan shellcode Anda melihat konstruksi ini banyak:

int (*ret)() = (int(*)())code;
ret();

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.

Casey
sumber
5

Deklarasi

void (*(*f[])())()

hanyalah cara mengatakan yang tidak jelas

Function f[]

dengan

typedef void (*ResultFunction)();

typedef ResultFunction (*Function)();

Dalam praktiknya, nama yang lebih deskriptif akan dibutuhkan alih-alih ResultFunction dan Function . Jika memungkinkan saya juga akan menentukan daftar parameter sebagai void.

August Karlstrom
sumber
4

Saya menemukan metode yang dijelaskan oleh Bruce Eckel sangat membantu dan mudah diikuti:

Menentukan pointer fungsi

Untuk menetapkan pointer ke fungsi yang tidak memiliki argumen dan tidak ada nilai balik, Anda mengatakan:

void (*funcPtr)();

Ketika Anda melihat definisi rumit seperti ini, cara terbaik untuk menyerang itu adalah mulai dari tengah dan mencari jalan keluar. "Mulai di tengah" berarti mulai dari nama variabel, yaitu funcPtr. “Mengerjakan jalan keluar Anda” berarti mencari ke kanan untuk barang terdekat (tidak ada dalam kasus ini; tanda kurung kanan membuat Anda pendek), kemudian melihat ke kiri (sebuah penunjuk yang ditandai oleh tanda bintang), lalu melihat ke kanan (sebuah daftar argumen kosong menunjukkan fungsi yang tidak mengambil argumen), kemudian melihat ke kiri (void, yang menunjukkan fungsi tidak memiliki nilai balik). Gerakan kanan-kiri-kanan ini berfungsi dengan sebagian besar deklarasi.

Untuk mengulas, "mulai di tengah" ("funcPtr adalah ..."), ke kanan (tidak ada di sana - Anda dihentikan oleh tanda kurung yang tepat), ke kiri dan cari '*' (" ... arahkan ke ... "), ke kanan dan temukan daftar argumen kosong (" ... fungsi yang tidak memerlukan argumen ... "), pergi ke kiri dan temukan kekosongan (" funcPtr adalah pointer ke fungsi yang tidak menggunakan argumen dan mengembalikan void ”).

Anda mungkin bertanya-tanya mengapa * funcPtr membutuhkan tanda kurung. Jika Anda tidak menggunakannya, kompiler akan melihat:

void *funcPtr();

Anda akan mendeklarasikan fungsi (yang mengembalikan void *) daripada mendefinisikan variabel. Anda dapat menganggap kompiler melalui proses yang sama dengan yang Anda lakukan ketika ia menentukan apa yang seharusnya merupakan deklarasi atau definisi. Dibutuhkan tanda kurung itu untuk "bertabrakan" sehingga kembali ke kiri dan menemukan '*', alih-alih melanjutkan ke kanan dan menemukan daftar argumen kosong.

Deklarasi & definisi yang rumit

Sebagai tambahan, setelah Anda mengetahui bagaimana sintaks deklarasi C dan C ++ bekerja, Anda dapat membuat item yang jauh lebih rumit. Misalnya:

//: C03:ComplicatedDefinitions.cpp

/* 1. */     void * (*(*fp1)(int))[10];

/* 2. */     float (*(*fp2)(int,int,float))(int);

/* 3. */     typedef double (*(*(*fp3)())[10])();
             fp3 a;

/* 4. */     int (*(*f4())[10])();


int main() {} ///:~ 

Telusuri masing-masing dan gunakan pedoman kanan-kiri untuk mencari tahu. Nomor 1 mengatakan "fp1 adalah pointer ke fungsi yang mengambil argumen integer dan mengembalikan pointer ke array 10 pointer kosong."

Nomor 2 mengatakan "fp2 adalah pointer ke fungsi yang mengambil tiga argumen (int, int, dan float) dan mengembalikan pointer ke fungsi yang mengambil argumen integer dan mengembalikan float."

Jika Anda membuat banyak definisi yang rumit, Anda mungkin ingin menggunakan typedef. Nomor 3 menunjukkan bagaimana typedef menyimpan pengetikan deskripsi yang rumit setiap saat. Dikatakan "fp3 adalah pointer ke fungsi yang tidak membutuhkan argumen dan mengembalikan pointer ke array 10 pointer ke fungsi yang tidak mengambil argumen dan mengembalikan ganda." Lalu tertulis "a adalah salah satu dari tipe fp3 ini." typedef umumnya berguna untuk membangun deskripsi rumit dari yang sederhana.

Nomor 4 adalah deklarasi fungsi bukan definisi variabel. Dikatakan "f4 adalah fungsi yang mengembalikan pointer ke array 10 pointer ke fungsi yang mengembalikan integer."

Anda jarang membutuhkan deklarasi dan definisi yang rumit seperti ini. Namun, jika Anda melakukan latihan mencari tahu mereka, Anda bahkan tidak akan sedikit terganggu dengan yang sedikit rumit yang mungkin Anda temui dalam kehidupan nyata.

Diambil dari: Berpikir dalam C ++ Volume 1, edisi kedua, bab 3, bagian "Function Addresses" oleh Bruce Eckel.

pengguna3496846
sumber
4

Ingat aturan ini untuk C menyatakan
Dan diutamakan tidak akan pernah ragu:
Mulailah dengan akhiran, lanjutkan dengan awalan,
Dan baca kedua set dari dalam, keluar.
- saya, pertengahan 1980-an

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:

if if if = then then then = else else else = if then ...
keshlam
sumber
2
Pernyataan PL / I adalah IF IF = THEN THEN THEN = ELSE ELSE ELSE = ENDIF ENDIFdan diuraikan sebagai if (IF == THEN) then (THEN = ELSE) else (ELSE = ENDIF).
Cole Johnson
Saya pikir ada versi yang mengambil satu langkah lebih jauh dengan menggunakan ekspresi kondisional IF / THEN / ELSE (setara dengan C's? :), yang membuat set ketiga ke dalam campuran ... tetapi sudah beberapa dekade dan mungkin memiliki tergantung pada dialek bahasa tertentu. Poin tetap bahwa bahasa apa pun memiliki setidaknya satu bentuk patologis.
keshlam
4

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:

char *ar[10][10];

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:

char *(ar[10][10]);

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" :)

David Anderson
sumber
3
  • kosong (*(*f[]) ()) ()

Menyelesaikan void>>

  • (*(*f[]) ()) () = batal

Resoiving ()>>

  • (* (*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 [ ]>>

  • f = larik (penunjuk ke (fungsi kembali (penunjuk ke (fungsi kembali (batal))))))
Shubham
sumber