Apa Pertanyaan Provokatif!
Bahkan pemindaian sepintas terhadap respons dan komentar di utas ini akan mengungkapkan bagaimana emotifnya kueri Anda yang tampaknya sederhana dan lurus ke depan ternyata.
Seharusnya tidak mengejutkan.
Inarguably, kesalahpahaman di sekitar konsep dan penggunaan dari pointer merupakan dominan penyebab serius kegagalan dalam pemrograman pada umumnya.
Pengakuan atas kenyataan ini sudah terbukti dalam ubikuitas bahasa yang dirancang khusus untuk mengatasi, dan lebih baik untuk menghindari tantangan yang diperkenalkan oleh pointer sama sekali. Pikirkan C ++ dan turunan lainnya dari C, Java dan relasinya, Python dan skrip lainnya - hanya sebagai yang lebih menonjol dan lazim, dan kurang lebih teratur dalam menangani masalah ini.
Mengembangkan pemahaman yang lebih dalam tentang prinsip-prinsip yang mendasarinya, oleh karena itu harus relevan dengan setiap individu yang bercita-cita untuk keunggulan dalam pemrograman - terutama di tingkat sistem .
Saya membayangkan inilah tepatnya yang ditunjukkan oleh guru Anda.
Dan sifat C membuatnya menjadi kendaraan yang nyaman untuk eksplorasi ini. Kurang jelas daripada perakitan - meskipun mungkin lebih mudah dipahami - dan masih jauh lebih eksplisit daripada bahasa berdasarkan abstraksi yang lebih dalam dari lingkungan eksekusi.
Dirancang untuk memfasilitasi terjemahan deterministik dari maksud programmer ke dalam instruksi yang dapat dipahami mesin, C adalah bahasa tingkat sistem . Meskipun diklasifikasikan sebagai tingkat tinggi, itu benar-benar termasuk dalam kategori 'sedang'; tetapi karena tidak ada seperti itu, penunjukan 'sistem' harus cukup.
Karakteristik ini sebagian besar bertanggung jawab untuk menjadikannya bahasa pilihan untuk driver perangkat , kode sistem operasi , dan implementasi yang disematkan . Lebih jauh, alternatif yang lebih disukai dalam aplikasi di mana efisiensi optimal adalah yang terpenting; di mana itu berarti perbedaan antara kelangsungan hidup dan kepunahan, dan oleh karena itu merupakan keharusan sebagai lawan dari kemewahan. Dalam kasus seperti itu, kenyamanan portabilitas yang menarik kehilangan semua daya pikatnya, dan memilih untuk kinerja yang kurang berkilau dari penyebut yang paling tidak umum menjadi pilihan merugikan yang tak terduga .
Apa yang membuat C - dan beberapa turunannya - cukup istimewa, adalah bahwa ia memungkinkan penggunanya mengendalikan sepenuhnya - ketika itu yang mereka inginkan - tanpa memaksakan tanggung jawab terkait kepada mereka ketika mereka tidak. Namun demikian, tidak pernah menawarkan lebih dari tertipis dari isolasi dari mesin , karenanya penggunaan yang tepat menuntut menuntut pemahaman konsep pointer .
Pada dasarnya, jawaban atas pertanyaan Anda sangat sederhana dan manis - sebagai konfirmasi atas kecurigaan Anda. Disediakan , bagaimanapun, salah satu yang melekat syarat penting untuk setiap konsep dalam pernyataan ini:
- Tindakan memeriksa, membandingkan, dan memanipulasi pointer selalu dan tentu saja valid, sedangkan kesimpulan yang diperoleh dari hasil tergantung pada validitas nilai yang terkandung, dan karenanya tidak perlu.
Yang pertama keduanya selalu aman dan berpotensi tepat , sedangkan yang terakhir hanya bisa tepat ketika telah ditetapkan sebagai aman . Anehnya - bagi sebagian orang - jadi menetapkan validitas yang terakhir tergantung pada dan menuntut yang pertama.
Tentu saja, bagian dari kebingungan muncul dari efek rekursi yang secara inheren hadir dalam prinsip penunjuk - dan tantangan yang ditimbulkan dalam membedakan konten dari alamat.
Anda telah menduga dengan benar ,
Saya dituntun untuk berpikir bahwa pointer apa pun dapat dibandingkan dengan pointer lain, terlepas dari mana mereka menunjuk secara individual. Selain itu, saya pikir pointer aritmatika antara dua pointer baik-baik saja, tidak peduli di mana mereka menunjuk secara individual karena aritmatika hanya menggunakan memori alamat toko pointer.
Dan beberapa kontributor telah menegaskan: pointer hanya angka. Terkadang sesuatu lebih dekat ke bilangan kompleks , tetapi masih tidak lebih dari angka.
Perasaan lucu di mana pertikaian ini diterima di sini mengungkapkan lebih banyak tentang sifat manusia daripada pemrograman, tetapi tetap layak dicatat dan dielaborasi. Mungkin kita akan melakukannya nanti ...
Ketika satu komentar mulai mengisyaratkan; semua kebingungan dan kekhawatiran ini berasal dari kebutuhan untuk membedakan apa yang valid dari apa yang aman , tetapi itu adalah penyederhanaan yang berlebihan. Kita juga harus membedakan mana yang fungsional dan apa yang dapat diandalkan , apa yang praktis dan apa yang pantas , dan lebih jauh lagi: apa yang pantas dalam keadaan tertentu dari apa yang mungkin pantas dalam arti yang lebih umum . Apalagi; perbedaan antara kesesuaian dan kesopanan .
Menjelang itu, pertama kita perlu menghargai tepat apa pointer adalah .
- Anda telah menunjukkan cengkeraman yang kuat pada konsep, dan seperti yang lain mungkin menemukan ilustrasi ini sangat sederhana, tetapi tingkat kebingungan jelas di sini menuntut kesederhanaan dalam klarifikasi.
Seperti yang telah ditunjukkan oleh beberapa orang: istilah pointer hanyalah nama khusus untuk apa yang sekadar indeks , dan dengan demikian tidak lebih dari angka lainnya .
Ini harus sudah jelas dengan mempertimbangkan fakta bahwa semua komputer arus utama kontemporer adalah mesin biner yang tentu saja bekerja secara eksklusif dengan dan pada angka . Komputasi kuantum dapat mengubah itu, tetapi itu sangat tidak mungkin, dan itu belum dewasa.
Secara teknis, seperti yang telah Anda catat, pointer adalah alamat yang lebih akurat ; wawasan yang jelas yang secara alami memperkenalkan analogi yang bermanfaat dari menghubungkan mereka dengan 'alamat' rumah, atau plot di jalan.
Dalam model memori datar : seluruh memori sistem disusun dalam satu urutan linier tunggal: semua rumah di kota terletak di jalan yang sama, dan setiap rumah diidentifikasi secara unik dengan jumlahnya saja. Sederhana dan menyenangkan.
Dalam skema tersegmentasi : organisasi hierarkis jalan bernomor diperkenalkan di atas rumah bernomor sehingga diperlukan alamat komposit.
- Beberapa implementasi masih lebih berbelit-belit, dan totalitas 'jalan' yang berbeda tidak perlu dijumlahkan dengan urutan yang berdekatan, tetapi tidak ada yang mengubah apa pun tentang yang mendasarinya.
- Kami harus dapat menguraikan setiap tautan hierarkis tersebut kembali menjadi organisasi yang rata. Semakin kompleks organisasi, semakin banyak rintangan yang harus kita lewati untuk melakukannya, tetapi itu harus dimungkinkan. Memang, ini juga berlaku untuk 'mode nyata' di x86.
- Kalau tidak, pemetaan tautan ke lokasi tidak akan bersifat bijektif , karena eksekusi yang andal - pada tingkat sistem - menuntut bahwa itu HARUS .
- banyak alamat tidak boleh dipetakan ke lokasi memori tunggal, dan
- alamat tunggal tidak boleh memetakan ke beberapa lokasi memori.
Membawa kami ke putaran lebih lanjut yang mengubah teka-teki menjadi kusut yang begitu rumit . Di atas, itu bijaksana untuk menyarankan bahwa pointer adalah alamat, demi kesederhanaan dan kejelasan. Tentu saja ini tidak benar. Sebuah pointer adalah bukan alamat; pointer adalah referensi ke alamat , itu berisi alamat . Seperti olahraga amplop referensi ke rumah. Merenungkan ini dapat membuat Anda melihat sekilas apa yang dimaksud dengan saran rekursi yang terkandung dalam konsep. Masih; kami hanya memiliki begitu banyak kata, dan berbicara tentang alamat referensi ke alamatdan semacamnya, segera menghentikan sebagian besar otak pada pengecualian kode-op yang tidak valid . Dan sebagian besar, niat sudah siap dikumpulkan dari konteks, jadi mari kita kembali ke jalan.
Pekerja pos di kota imajiner kita ini sangat mirip dengan yang kita temukan di dunia 'nyata'. Tidak ada yang cenderung menderita stroke ketika Anda berbicara atau menanyakan tentang alamat yang tidak valid , tetapi setiap yang terakhir akan menolak ketika Anda meminta mereka untuk bertindak berdasarkan informasi itu.
Misalkan hanya ada 20 rumah di jalan tunggal kami. Lebih lanjut berpura-pura bahwa beberapa jiwa yang salah arah, atau disleksia telah mengarahkan sebuah surat, yang sangat penting, ke nomor 71. Sekarang, kita dapat bertanya kepada pembawa pesan kita Frank, apakah ada alamat seperti itu, dan dia akan dengan sederhana dan tenang melaporkan: tidak . Kita bahkan bisa berharap dia memperkirakan seberapa jauh di luar jalan lokasi ini akan terletak jika memang ada: kira-kira 2,5 kali lebih jauh dari akhir. Semua ini tidak akan membuatnya putus asa. Namun, jika kita memintanya untuk mengirimkan surat ini, atau untuk mengambil item dari tempat itu, dia kemungkinan besar akan terus terang tentang ketidaksenangannya , dan penolakan untuk mematuhinya.
Pointer itu adil alamat, dan alamat hanyalah angka.
Verifikasi output dari yang berikut:
void foo( void *p ) {
printf(“%p\t%zu\t%d\n”, p, (size_t)p, p == (size_t)p);
}
Sebutkan pada pointer sebanyak yang Anda suka, valid atau tidak. Silakan lakukan posting temuan Anda jika gagal pada platform Anda, atau Anda (kontemporer) compiler mengeluh.
Sekarang, karena pointer yang hanya nomor, itu pasti berlaku untuk membandingkan mereka. Di satu sisi inilah yang ditunjukkan oleh guru Anda. Semua pernyataan berikut ini benar-benar valid - dan layak! - C, dan ketika dikompilasi akan berjalan tanpa menemui masalah , meskipun pointer tidak perlu diinisialisasi dan nilai-nilai yang dikandungnya mungkin tidak terdefinisi :
- Kami hanya menghitung
result
secara eksplisit demi kejelasan , dan mencetaknya untuk memaksa kompiler menghitung apa yang seharusnya menjadi kode mati yang mubazir.
void foo( size_t *a, size_t *b ) {
size_t result;
result = (size_t)a;
printf(“%zu\n”, result);
result = a == b;
printf(“%zu\n”, result);
result = a < b;
printf(“%zu\n”, result);
result = a - b;
printf(“%zu\n”, result);
}
Tentu saja, program ini salah bentuk ketika a atau b tidak terdefinisi (baca: tidak diinisialisasi dengan benar ) pada titik pengujian, tetapi itu sama sekali tidak relevan dengan bagian diskusi kita ini. Cuplikan ini, seperti juga pernyataan berikut, dijamin - dengan 'standar' - untuk dikompilasi dan dijalankan dengan sempurna, meskipun IN- validitas pointer yang terlibat.
Masalah hanya muncul ketika pointer tidak valid ditereferensi . Ketika kami meminta Frank untuk mengambil atau mengirim di alamat yang tidak valid dan tidak ada.
Diberikan pointer sembarang:
int *p;
Sementara pernyataan ini harus mengkompilasi dan menjalankan:
printf(“%p”, p);
... sebagaimana mestinya:
size_t foo( int *p ) { return (size_t)p; }
... berikut dua, kontras, akan tetap mudah mengkompilasi, tetapi gagal di eksekusi kecuali pointer adalah sah - yang kita disini hanya berarti bahwa itu referensi alamat dimana aplikasi ini telah diberikan akses :
printf(“%p”, *p);
size_t foo( int *p ) { return *p; }
Seberapa halus perubahannya? Perbedaannya terletak pada perbedaan antara nilai pointer - yang merupakan alamat, dan nilai konten: dari rumah di nomor itu. Tidak ada masalah muncul sampai pointer dereferenced ; sampai upaya dilakukan untuk mengakses alamat yang ditautkan. Dalam mencoba mengirimkan atau mengambil paket di luar bentangan jalan ...
Dengan perluasan, prinsip yang sama harus berlaku untuk contoh yang lebih kompleks, termasuk kebutuhan yang disebutkan di atas untuk menetapkan validitas yang diperlukan:
int* validate( int *p, int *head, int *tail ) {
return p >= head && p <= tail ? p : NULL;
}
Perbandingan relasional dan aritmatika menawarkan utilitas yang identik untuk menguji kesetaraan, dan pada prinsipnya valid - pada prinsipnya. Namun , apa hasil dari perhitungan seperti itu ditunjukkan, adalah masalah yang sama sekali berbeda - dan justru masalah yang dibahas oleh kutipan yang Anda sertakan.
Dalam C, array adalah buffer yang bersebelahan, sebuah rangkaian linear lokasi memori yang tidak terputus. Perbandingan dan aritmatika diterapkan pada petunjuk bahwa lokasi referensi dalam rangkaian singular semacam itu secara alami, dan jelas bermakna dalam kaitannya satu sama lain, dan dengan 'larik' ini (yang hanya diidentifikasi oleh pangkalan). Hal yang sama berlaku untuk setiap blok yang dialokasikan melalui malloc
, atau sbrk
. Karena hubungan ini implisit , kompiler dapat membangun hubungan yang valid di antara mereka, dan karena itu dapat yakin bahwa perhitungan akan memberikan jawaban yang diantisipasi.
Pertunjukan senam yang sama pada pointer yang referensi yang berbeda blok atau array tidak menawarkan apapun seperti yang melekat , dan jelas utilitas. Terlebih lagi karena hubungan apa pun yang ada pada satu saat dapat dibatalkan oleh realokasi yang mengikuti, di mana itu sangat mungkin berubah, bahkan dapat dibalik. Dalam kasus seperti itu kompiler tidak dapat memperoleh informasi yang diperlukan untuk membangun kepercayaan yang dimilikinya pada situasi sebelumnya.
Anda , bagaimanapun, sebagai programmer, mungkin memiliki pengetahuan seperti itu! Dan dalam beberapa kasus wajib mengeksploitasi itu.
Ada ADALAH , oleh karena itu, keadaan di mana BAHKAN INI sepenuhnya VALID dan sempurna PROPER.
Bahkan, itulah yang malloc
harus dilakukan sendiri secara internal ketika saatnya tiba untuk mencoba menggabungkan blok reklamasi - pada sebagian besar arsitektur. Hal yang sama berlaku untuk pengalokasi sistem operasi, seperti itu di belakang sbrk
; jika lebih jelas , sering , pada entitas yang lebih berbeda , lebih kritis - dan relevan juga pada platform di mana ini malloc
mungkin tidak. Dan berapa banyak dari mereka yang tidak ditulis dalam C?
Validitas, keamanan, dan keberhasilan suatu tindakan tidak dapat dihindari adalah konsekuensi dari tingkat wawasan yang menjadi dasar pemikiran dan penerapannya.
Dalam kutipan yang Anda tawarkan, Kernighan dan Ritchie membahas masalah yang terkait erat, namun tetap terpisah. Mereka mendefinisikan yang keterbatasan dari bahasa , dan menjelaskan bagaimana Anda dapat memanfaatkan kemampuan compiler untuk melindungi Anda dengan setidaknya mendeteksi konstruksi berpotensi keliru. Mereka menggambarkan panjangnya mekanisme yang bisa - dirancang - untuk digunakan untuk membantu Anda dalam tugas pemrograman Anda. Kompiler adalah pelayan Anda , Anda adalah tuannya. Namun, seorang guru yang bijak adalah seorang yang akrab dengan kemampuan berbagai pelayannya.
Dalam konteks ini, perilaku tidak terdefinisi berfungsi untuk menunjukkan potensi bahaya dan kemungkinan bahaya; bukan untuk menyiratkan malapetaka yang sudah dekat, ireversibel, atau akhir dunia seperti yang kita kenal. Ini hanya berarti bahwa kita - 'yang berarti kompiler' - tidak dapat membuat dugaan tentang apa hal ini mungkin, atau mewakili dan karena alasan ini kami memilih untuk mencuci tangan masalah ini. Kami tidak akan bertanggung jawab atas kesalahan yang mungkin terjadi akibat penggunaan, atau salah penggunaan fasilitas ini .
Akibatnya, ia hanya mengatakan: 'Di luar titik ini, koboi : Anda sendirian ...'
Profesor Anda berusaha menunjukkan nuansa yang lebih halus kepada Anda.
Perhatikan betapa hati - hati mereka dalam membuat contoh mereka; dan bagaimana rapuh itu masih adalah. Dengan mengambil alamat a
, di
p[0].p0 = &a;
kompiler dipaksa untuk mengalokasikan penyimpanan aktual untuk variabel, daripada menempatkannya dalam register. Ini menjadi variabel otomatis, namun, programmer tidak memiliki kendali atas tempat yang ditugaskan, dan karenanya tidak dapat membuat dugaan yang valid tentang apa yang akan mengikutinya. Itulah sebabnya a
harus ditetapkan sama dengan nol agar kode berfungsi seperti yang diharapkan.
Hanya mengubah baris ini:
char a = 0;
untuk ini:
char a = 1; // or ANY other value than 0
menyebabkan perilaku program menjadi tidak terdefinisi . Minimal, jawaban pertama sekarang adalah 1; tetapi masalahnya jauh lebih jahat.
Sekarang kode mengundang bencana.
Meskipun masih benar-benar valid dan bahkan sesuai dengan standar , itu sekarang tidak terbentuk dan meskipun yakin untuk dikompilasi, mungkin gagal dalam eksekusi dengan berbagai alasan. Untuk saat ini ada beberapa masalah - tidak ada dimana compiler adalah mampu untuk mengenali.
strcpy
akan mulai dari alamat a
, dan melanjutkan melampaui ini untuk mengkonsumsi - dan mentransfer - byte demi byte, sampai bertemu dengan nol.
The p1
pointer telah diinisialisasi ke blok tepat 10 bytes.
Jika a
kebetulan ditempatkan di ujung blok dan proses tidak memiliki akses ke yang berikut, pembacaan berikutnya - dari p0 [1] - akan memperoleh segfault. Skenario ini tidak mungkin pada arsitektur x86, tetapi dimungkinkan.
Jika area di luar alamat a
dapat diakses, tidak akan terjadi kesalahan baca, tetapi program masih belum disimpan dari kemalangan.
Jika nol byte terjadi dalam sepuluh dimulai pada alamat a
, itu masih dapat bertahan, karena itu strcpy
akan berhenti dan setidaknya kita tidak akan menderita pelanggaran tulis.
Jika tidak salah untuk membaca salah, tetapi tidak ada byte nol terjadi dalam rentang 10 ini, strcpy
akan terus berlanjut dan berusaha untuk menulis di luar blok yang dialokasikan oleh malloc
.
Jika area ini tidak dimiliki oleh proses, segfault harus segera dipicu.
Masih lebih bencana - dan halus situasi --- muncul ketika blok berikut ini dimiliki oleh proses, untuk maka kesalahan tidak dapat dideteksi, tidak ada sinyal dapat diangkat, dan sehingga mungkin 'muncul' masih 'bekerja' , sementara itu sebenarnya akan menimpa data lain, struktur manajemen pengalokasi Anda, atau bahkan kode (dalam lingkungan operasi tertentu).
Ini adalah mengapa pointer terkait bug bisa begitu sulit untuk melacak . Bayangkan baris-baris ini terkubur dalam ribuan baris kode terkait yang rumit, yang telah ditulis orang lain, dan Anda diarahkan untuk menyelidiki.
Meskipun demikian , program tersebutmasih harus dikompilasi, karena tetap valid sempurna dan sesuai standar C.
Jenis kesalahan ini, tidak ada standar dan tidak ada kompiler dapat melindungi mereka yang tidak waspada. Saya membayangkan itulah yang ingin mereka ajarkan kepada Anda.
Orang paranoid terus berusaha untuk mengubah dengan sifat dari C untuk membuang kemungkinan-kemungkinan bermasalah dan menyelamatkan kita dari diri kita sendiri; tapi itu tidak jujur . Ini adalah tanggung jawab yang harus kita terima ketika kita memilih untuk mengejar kekuasaan dan memperoleh kebebasan yang ditawarkan kontrol mesin yang lebih langsung dan komprehensif . Promotor dan pengejar kesempurnaan dalam kinerja tidak akan pernah menerima apa pun yang kurang.
Portabilitas dan sifat umum yang diwakilinya merupakan pertimbangan yang terpisah secara mendasar dan semua yangberusaha ditangani oleh standar :
Dokumen ini menentukan bentuk dan menetapkan interpretasi program yang diekspresikan dalam bahasa pemrograman C. Tujuannya adalah untuk mempromosikan portabilitas , keandalan, perawatan, dan pelaksanaan program bahasa C yang efisien pada berbagai sistem komputasi .
Itulah sebabnya sangat tepat untuk membedakannya dari definisi dan spesifikasi teknis bahasa itu sendiri. Bertentangan dengan apa yang banyak orang percayai, generalitas adalah antitesis terhadap pengecualian dan keteladanan .
Untuk menyimpulkan:
- Memeriksa dan memanipulasi pointer sendiri selalu valid dan sering bermanfaat . Interpretasi hasil, mungkin, atau mungkin tidak bermakna, tetapi malapetaka tidak pernah diundang sampai penunjuk dereferensi ; hingga upaya dilakukan untuk mengakses alamat yang ditautkan.
Kalau ini tidak benar, pemrograman seperti yang kita tahu - dan menyukainya - tidak akan mungkin terjadi.
C
dengan apa yang aman diC
. Membandingkan dua pointer dengan tipe yang sama selalu dapat dilakukan (memeriksa kesetaraan, misalnya), menggunakan pointer aritmatika dan membandingkan>
dan<
hanya aman ketika digunakan dalam array yang diberikan (atau blok memori).