Mengapa sintaks C untuk array, pointer, dan fungsi dirancang dengan cara ini?

16

Setelah melihat (dan bertanya!) Begitu banyak pertanyaan yang mirip

Apa yang dimaksud int (*f)(int (*a)[5])dengan C?

dan bahkan melihat bahwa mereka membuat program untuk membantu orang memahami sintaks C, saya bertanya-tanya:

Mengapa sintaks C dirancang dengan cara ini?

Misalnya, jika saya mendesain pointer, saya akan menerjemahkan "pointer ke array 10-elemen pointer" menjadi

int*[10]* p;

dan tidak

int* (*p)[10];

yang saya rasa kebanyakan orang akan setuju jauh lebih mudah.

Jadi saya bertanya-tanya, mengapa, eh, sintaks tidak intuitif? Apakah ada masalah khusus yang diselesaikan oleh sintaksis (mungkin ambiguitas?) Yang tidak saya sadari?

pengguna541686
sumber
2
Anda tahu tidak ada jawaban nyata untuk ini, dan pertanyaan seperti itu. Baik? Apa yang akan Anda dapatkan hanyalah tebakan.
BЈовић
7
@ VJo - mungkin ada jawaban "nyata" (mis. Objektif) - penulis bahasa dan komite standar sama-sama telah secara eksplisit membenarkan (atau setidaknya menjelaskan) banyak dari keputusan ini.
detly
Saya tidak berpikir sintaks yang Anda usulkan lebih atau kurang "intuitif" daripada sintaks C. C adalah apa adanya; setelah Anda mempelajarinya, Anda tidak akan memiliki pertanyaan ini lagi. Jika Anda belum mempelajarinya ... mungkin itulah masalah sebenarnya.
Caleb
1
@ Caleb: Lucu bagaimana Anda menyimpulkan itu dengan mudah, karena saya mempelajarinya dan saya masih memiliki pertanyaan ini ...
user541686
1
The cdeclPerintah ini sangat berguna untuk decoding C deklarasi kompleks. Ada juga antarmuka web di cdecl.org .
Keith Thompson

Jawaban:

16

Pemahaman saya tentang sejarahnya adalah bahwa itu didasarkan pada dua poin utama ...

Pertama, penulis bahasa lebih suka membuat sintaks variabel-sentris daripada tipe-sentris. Artinya, mereka ingin seorang programmer untuk melihat deklarasi dan berpikir "jika saya menulis ekspresi *func(arg), itu akan menghasilkan int; jika saya menulis *arg[N]saya akan memiliki float" daripada " funcharus menjadi pointer ke fungsi yang mengambil ini dan mengembalikan itu ".

The C entri di Wikipedia mengklaim bahwa:

Gagasan Ritchie adalah untuk menyatakan pengidentifikasi dalam konteks yang menyerupai penggunaannya: "deklarasi mencerminkan penggunaan".

... mengutip p122 dari K & R2 yang, sayangnya, saya tidak perlu menyerahkan untuk menemukan kutipan diperpanjang untuk Anda.

Kedua sebenarnya sangat, sangat sulit untuk menghasilkan sintaks untuk deklarasi yang konsisten ketika Anda berurusan dengan tingkat tipuan yang sewenang-wenang. Contoh Anda mungkin bekerja dengan baik untuk mengekspresikan tipe yang Anda pikirkan di luar sana, tetapi apakah itu skala ke fungsi mengambil pointer ke array jenis tersebut, dan mengembalikan beberapa kekacauan mengerikan lainnya? (Mungkin benar, tetapi apakah Anda memeriksa? Bisakah Anda membuktikannya? ).

Ingat, bagian dari keberhasilan C adalah karena fakta bahwa kompiler ditulis untuk banyak platform yang berbeda, dan mungkin lebih baik untuk mengabaikan beberapa tingkat keterbacaan demi membuat kompiler lebih mudah untuk ditulis.

Karena itu, saya bukan ahli tata bahasa atau penulisan kompiler. Tapi saya cukup tahu untuk tahu ada banyak yang tahu;)

detly
sumber
2
"membuat kompiler lebih mudah untuk ditulis" ... kecuali C terkenal karena sulit diurai (hanya diatapi oleh C ++).
Jan Hudec
1
@ JanHudec - Ya ... ya. Itu bukan pernyataan kedap air. Tetapi sementara C tidak mungkin diurai sebagai tata bahasa bebas konteks, begitu satu orang menemukan cara untuk menguraikannya, ini tidak lagi menjadi langkah yang sulit. Dan faktanya, ini sangat produktif di masa-masa awalnya karena orang-orang dapat dengan mudah membuat kompiler, jadi K&R pasti telah mencapai keseimbangan. (Dalam film terkenal Richard The Rise of "Worse is Better" , ia menerima begitu saja - dan meratapi - fakta bahwa mudah untuk menulis kompiler C untuk platform baru.)
Detly
Ngomong-ngomong, aku senang bisa dikoreksi soal ini - aku tidak tahu banyak tentang penguraian dan tata bahasa. Saya akan lebih banyak menarik kesimpulan dari fakta sejarah.
detly
12

Banyak keanehan dari bahasa C dapat dijelaskan dengan cara komputer bekerja saat itu dirancang. Ada jumlah memori penyimpanan yang sangat terbatas, sehingga sangat penting untuk meminimalkan ukuran file kode sumber itu sendiri. Praktek pemrograman kembali pada tahun 70-an dan 80-an adalah untuk memastikan kode sumber mengandung karakter sesedikit mungkin, dan lebih disukai tidak ada komentar kode sumber yang berlebihan.

Ini tentu saja konyol hari ini, dengan ruang penyimpanan yang tidak terbatas pada hard drive. Tetapi itu adalah bagian dari alasan mengapa C memiliki sintaks aneh seperti itu pada umumnya.


Mengenai pointer array khusus, contoh kedua Anda seharusnya int (*p)[10];(yeah sintaksnya sangat membingungkan). Saya mungkin akan membacanya sebagai "int pointer to array of sepuluh" ... yang agak masuk akal. Jika bukan untuk kurung, kompiler akan menafsirkannya sebagai array dari sepuluh petunjuk saja, yang akan memberikan deklarasi makna yang sama sekali berbeda.

Karena array pointer dan pointer fungsi keduanya memiliki sintaks yang cukup tidak jelas dalam C, hal yang masuk akal untuk dilakukan adalah mengetikkan keanehannya. Mungkin seperti ini:

Contoh tidak jelas:

int func (int (*arr_ptr)[10])
{
  return 0;
}

int main()
{
  int array[10];
  int (*arr_ptr)[10]  = &array;
  int (*func_ptr)(int(*)[10]) = &func;

  func_ptr(arr_ptr);
}

Contoh tidak setara, tidak jelas:

typedef int array_t[10];
typedef int (*funcptr_t)(array_t*);


int func (array_t* arr_ptr)
{
  return 0;
}

int main()
{
  int        array[10];
  array_t*   arr_ptr  = &array; /* non-obscure array pointer */
  funcptr_t  func_ptr = &func;  /* non-obscure function pointer */

  func_ptr(arr_ptr);
}

Hal-hal bisa menjadi lebih tidak jelas jika Anda berurusan dengan array pointer fungsi. Atau yang paling tidak jelas dari semuanya: fungsi mengembalikan fungsi pointer (sedikit berguna). Jika Anda tidak menggunakan typedefs untuk hal-hal seperti itu, Anda akan dengan cepat menjadi gila.


sumber
Ah, akhirnya jawaban yang masuk akal. :-) Saya ingin tahu bagaimana sintaks tertentu sebenarnya akan mengecilkan ukuran kode sumber, tapi bagaimanapun itu adalah ide yang masuk akal dan masuk akal. Terima kasih. +1
user541686
Saya akan mengatakan itu kurang tentang ukuran kode sumber dan lebih banyak tentang penulisan kompiler, tapi pasti +1 untuk "mengetikkan keanehan". Kesehatan mental saya meningkat secara dramatis pada hari saya menyadari saya bisa melakukan ini.
detly
2
[Kutipan diperlukan] pada hal ukuran kode sumber. Saya belum pernah mendengar tentang pembatasan seperti itu (meskipun mungkin itu adalah sesuatu yang "semua orang tahu").
Sean McMillan
1
Yah saya kode program di tahun 70-an di COBOL, Assembler, CORAL dan PL / 1 pada IBM, DEC dan kit XEROX dan saya tidak pernah PERNAH menemukan batasan ukuran kode sumber. Keterbatasan ukuran array, ukuran yang dapat dieksekusi, ukuran nama program - tetapi tidak pernah ukuran kode sumber.
James Anderson
1
@Sean McMillan: Saya tidak berpikir ukuran kode sumber adalah batasan (pertimbangkan bahwa pada saat itu bahasa verbose seperti Pascal cukup populer). Dan bahkan jika ini yang terjadi, saya pikir akan sangat mudah untuk pra-parsing kode sumber dan mengganti kata kunci panjang dengan kode satu-byte pendek (seperti misalnya beberapa penerjemah dasar yang digunakan untuk melakukan). Jadi saya menemukan argumen "C terse karena diciptakan pada periode ketika memori kurang tersedia" agak lemah.
Giorgio
7

Ini sangat sederhana: int *partinya *pint; int a[5]berarti itu a[i]int.

int (*f)(int (*a)[5])

Berarti itu *fadalah suatu fungsi, *aadalah array dari lima bilangan bulat, demikian fjuga fungsi mengambil pointer ke array dari lima bilangan bulat, dan mengembalikan int. Namun, dalam C tidak berguna untuk meneruskan pointer ke array.

Deklarasi C sangat jarang mendapatkan ini rumit.

Anda juga dapat mengklarifikasi menggunakan typedefs:

typedef int vec5[5];
int (*f)(vec5 *a);
kevin cline
sumber
4
Permintaan maaf jika ini terdengar kasar (saya tidak bermaksud demikian), tapi saya pikir Anda melewatkan seluruh inti pertanyaan ...: \
user541686
2
@Mehrdad: Saya tidak bisa memberi tahu Anda apa yang ada dalam pikiran Kernighan dan Ritchie; Saya memang memberi tahu Anda logika di balik sintaks. Saya tidak tahu tentang kebanyakan orang, tetapi saya tidak berpikir bahwa sintaks yang Anda sarankan lebih jelas.
kevin cline
Saya setuju - tidak biasa melihat deklarasi yang rumit ini.
Caleb
Desain C deklarasi mendahului typedef, const, volatile, dan kemampuan untuk menginisialisasi hal dalam deklarasi. Banyak ambiguitas sintaksis deklarasi yang menjengkelkan (mis. Apakah int const *p, *q;harus mengikat constke tipe atau deklarasi) tidak dapat muncul dalam bahasa yang dirancang semula. Saya berharap bahasa telah menambahkan titik dua antara tipe dan deklarand, tetapi memungkinkan penghilangan ketika menggunakan built-in "kata-kata" tipe tanpa kualifikasi. Arti int: const *p,*q;dan int const *: p,*q;pasti sudah jelas.
supercat
3

Saya pikir Anda harus mempertimbangkan * [] sebagai operator yang dilampirkan ke variabel. * ditulis sebelum variabel, [] setelah.

Mari kita baca ekspresi tipe

int* (*p)[10];

Elemen terdalam adalah p, variabel, oleh karena itu

p

berarti: p adalah variabel.

Sebelum variabel ada *, operator * selalu diletakkan sebelum ekspresi yang dimaksud, oleh karena itu,

(*p)

berarti: variabel p adalah sebuah pointer. Tanpa () operator [] ke kanan akan memiliki prioritas lebih tinggi, yaitu

**p[]

akan diurai sebagai

*(*(p[]))

Langkah selanjutnya adalah []: karena tidak ada lagi (), [] memiliki prioritas lebih tinggi daripada * luar, oleh karena itu

(*p)[]

berarti: (variabel p adalah pointer) ke array. Maka kita memiliki yang kedua *:

* (*p)[]

berarti: ((variabel p adalah pointer) ke array) dari pointer

Akhirnya Anda memiliki operator int (nama jenis), yang memiliki prioritas terendah:

int* (*p)[]

berarti: (((variabel p adalah pointer) ke array) dari pointer) ke integer.

Jadi seluruh sistem didasarkan pada ekspresi tipe dengan operator, dan masing-masing operator memiliki aturan diutamakan sendiri. Ini memungkinkan untuk mendefinisikan tipe yang sangat kompleks.

Giorgio
sumber
0

Tidak begitu sulit ketika Anda mulai berpikir dan bahasa C tidak pernah mudah. Dan int*[10]* pbenar-benar tidak mudah daripada int* (*p)[10] Dan apa jenis k akan masukint*[10]* p, k;

Dainius
sumber
2
k akan menjadi tinjauan kode yang gagal, saya dapat mengetahui apa yang akan dilakukan oleh kompiler, saya bahkan mungkin terganggu, tetapi saya tidak dapat menemukan apa yang dimaksudkan oleh programmer - gagal ............
mattnz
dan mengapa k akan gagal tinjauan kode?
Dainius
1
karena kode tidak dapat dibaca dan tidak dapat dipelihara. Kode tidak benar untuk diperbaiki, jelas benar dan cenderung tetap benar meskipun pemeliharaan. Fakta Anda harus bertanya apa yang akan menjadi k adalah tanda kode gagal memenuhi persyaratan dasar ini.
mattnz
1
Pada dasarnya ada 3 (dalam hal ini) deklarasi variabel dari berbagai jenis pada baris yang sama misalnya int * p, int i [10] dan int k. Itu tidak bisa diterima. Deklarasi berganda dari jenis yang sama dapat diterima, asalkan variabel memiliki beberapa bentuk hubungan misalnya lebar int, tinggi, kedalaman; Perlu diingat banyak orang memprogram menggunakan int * p, jadi whats i di 'int * p, i;'.
mattnz
1
Apa yang ingin dikatakan @mattnz adalah bahwa Anda bisa sepintar yang Anda inginkan, tetapi semuanya tidak ada artinya ketika niat Anda tidak jelas dan / atau kode Anda ditulis dengan buruk / tidak dapat dibaca. Hal-hal semacam ini sering mengakibatkan kode rusak dan membuang-buang waktu. Plus, pointer to intdan intbahkan bukan tipe yang sama, sehingga harus dinyatakan secara terpisah. Titik. Dengarkan pria itu. Dia memiliki 18k perwakilan karena suatu alasan.
Braden Best