Saya akan menginterpretasikan pertanyaan Anda sebagai dua pertanyaan: 1) mengapa ->
ada, dan 2) mengapa .
tidak secara otomatis mengubah referensi pointer. Jawaban untuk kedua pertanyaan memiliki akar sejarah.
Mengapa ->
bahkan ada?
Dalam salah satu versi bahasa C yang paling pertama (yang akan saya rujuk sebagai CRM untuk " Manual Referensi C ", yang datang dengan Unix Edisi ke-6 pada bulan Mei 1975), operator ->
memiliki makna yang sangat eksklusif, tidak identik dengan *
dan .
kombinasi
Bahasa C yang dijelaskan oleh CRM sangat berbeda dari bahasa C modern dalam banyak hal. Dalam CRM struct, anggota menerapkan konsep global byte offset , yang dapat ditambahkan ke nilai alamat apa pun tanpa batasan jenis. Yaitu semua nama semua anggota struct memiliki makna global independen (dan, oleh karena itu, harus unik). Misalnya Anda bisa mendeklarasikan
struct S {
int a;
int b;
};
dan nama a
akan berarti offset 0, sedangkan nama b
akan mewakili offset 2 (dengan asumsi int
tipe ukuran 2 dan tanpa bantalan). Bahasa mengharuskan semua anggota semua struct di unit terjemahan memiliki nama yang unik atau memiliki nilai offset yang sama. Misalnya dalam unit terjemahan yang sama Anda juga dapat mendeklarasikan
struct X {
int a;
int x;
};
dan itu akan baik-baik saja, karena namanya a
akan secara konsisten berarti offset 0. Tapi deklarasi tambahan ini
struct Y {
int b;
int a;
};
akan secara resmi tidak valid, karena berusaha "mendefinisikan kembali" a
sebagai offset 2 dan b
sebagai offset 0.
Dan di sinilah ->
operator masuk. Karena setiap nama anggota struct memiliki makna global mandiri, bahasa mendukung ekspresi seperti ini
int i = 5;
i->b = 42; /* Write 42 into `int` at address 7 */
100->a = 0; /* Write 0 into `int` at address 100 */
Tugas pertama ditafsirkan oleh kompiler sebagai "ambil alamat 5
, tambahkan offset 2
ke sana dan tetapkan 42
ke int
nilai di alamat yang dihasilkan". Yaitu di atas akan menugaskan 42
untuk int
nilai di alamat 7
. Perhatikan bahwa penggunaan ->
ini tidak peduli dengan jenis ekspresi di sisi kiri. Sisi kiri ditafsirkan sebagai nilai numerik alamat (baik itu pointer atau integer).
Semacam ini tipu daya itu tidak mungkin dengan *
dan .
kombinasi. Anda tidak bisa melakukannya
(*i).b = 42;
karena *i
sudah merupakan ekspresi yang tidak valid. The *
operator, karena terpisah dari .
, memberlakukan persyaratan jenis yang lebih ketat pada operand. Untuk memberikan kemampuan untuk mengatasi keterbatasan ini, CRM memperkenalkan ->
operator, yang independen dari jenis operan di sebelah kiri.
Seperti yang disebutkan Keith dalam komentar, perbedaan antara ->
dan *
+ .
kombinasi inilah yang disebut CRM sebagai "pelonggaran persyaratan" dalam 7.1.8: Kecuali untuk pelonggaran persyaratan yang E1
bertipe pointer, ekspresi E1−>MOS
persis sama dengan(*E1).MOS
Kemudian, di K&R C banyak fitur yang awalnya dijelaskan dalam CRM secara signifikan ulang. Gagasan "struct member sebagai global offset identifier" sepenuhnya dihapus. Dan fungsi ->
operator menjadi sepenuhnya identik dengan fungsi *
dan .
kombinasi.
Mengapa .
dereference pointer tidak bisa secara otomatis?
Sekali lagi, dalam versi CRM bahasa operan kiri .
Operator diperlukan untuk menjadi lvalue . Itulah satu - satunya persyaratan yang dikenakan pada operan itu (dan itulah yang membuatnya berbeda dari ->
, seperti dijelaskan di atas). Perhatikan bahwa CRM tidak memerlukan operan kiri .
untuk memiliki tipe struct. Itu hanya diperlukan untuk menjadi nilai, nilai apa pun . Ini berarti bahwa dalam versi CRM C Anda dapat menulis kode seperti ini
struct S { int a, b; };
struct T { float x, y, z; };
struct T c;
c.b = 55;
Dalam hal ini kompiler akan menulis 55
ke int
nilai yang diposisikan pada byte-offset 2 di blok memori kontinu yang dikenal sebagai c
, meskipun tipe struct T
tidak memiliki bidang bernama b
. Kompiler tidak akan peduli dengan tipe yang sebenarnya c
sama sekali. Yang dipedulikannya c
hanyalah nilai tinggi: semacam blok memori yang bisa ditulis.
Sekarang perhatikan bahwa jika Anda melakukan ini
S *s;
...
s.b = 42;
kode akan dianggap valid (karena s
juga merupakan lvalue) dan kompiler hanya akan mencoba untuk menulis data ke dalam pointer s
itu sendiri , pada byte-offset 2. Tidak perlu dikatakan, hal-hal seperti ini dapat dengan mudah mengakibatkan memori overrun, tetapi bahasa tidak peduli dengan masalah seperti itu.
Yaitu dalam versi bahasa ide yang Anda usulkan tentang operator overloading .
untuk tipe pointer tidak akan berfungsi: operator .
sudah memiliki makna yang sangat spesifik ketika digunakan dengan pointer (dengan pointer nilai atau dengan nilai apa pun sama sekali). Fungsionalitasnya sangat aneh, tidak diragukan lagi. Tetapi itu ada di sana pada saat itu.
Tentu saja, fungsi yang aneh ini bukan alasan yang sangat kuat untuk tidak memperkenalkan .
operator kelebihan beban untuk pointer (seperti yang Anda sarankan) dalam versi ulang C - K&R C. Tapi itu belum dilakukan. Mungkin pada waktu itu ada beberapa kode lama yang ditulis dalam versi CRM C yang harus didukung.
(URL untuk Manual Referensi C 1975 mungkin tidak stabil. Salinan lain, mungkin dengan beberapa perbedaan halus, ada di sini .)