Dalam K&R (The C Programming Language 2nd Edition) bab 5 saya membaca yang berikut:
Pertama, pointer dapat dibandingkan dalam kondisi tertentu. Jika
p
danq
arahkan ke anggota array yang sama, hubungan itu seperti==
,!=
,<
,>=
, dll bekerja dengan baik.
Yang tampaknya menyiratkan bahwa hanya pointer yang menunjuk ke array yang sama dapat dibandingkan.
Namun ketika saya mencoba kode ini
char t = 't';
char *pt = &t;
char x = 'x';
char *px = &x;
printf("%d\n", pt > px);
1
dicetak ke layar.
Pertama-tama, saya pikir saya akan mendapatkan undefined atau beberapa tipe atau kesalahan, karena pt
dan px
tidak menunjuk ke array yang sama (setidaknya dalam pemahaman saya).
Juga pt > px
karena kedua pointer menunjuk ke variabel yang disimpan di stack, dan stack tumbuh turun, sehingga alamat memori t
lebih besar dari x
? Mengapa pt > px
itu benar?
Saya semakin bingung ketika malloc dibawa masuk. Juga di K&R di bab 8.7 berikut ini ditulis:
Masih ada satu asumsi, bagaimanapun, bahwa pointer ke blok yang berbeda dikembalikan oleh
sbrk
dapat dibandingkan secara bermakna. Ini tidak dijamin oleh standar yang memungkinkan perbandingan pointer hanya dalam sebuah array. Jadi versimalloc
ini portabel hanya di antara mesin-mesin yang perbandingan pointer umumnya bermakna.
Saya tidak punya masalah membandingkan pointer yang menunjuk ke ruang malloced di heap ke pointer yang menunjuk ke variabel stack.
Misalnya, kode berikut ini berfungsi dengan baik, dengan 1
dicetak:
char t = 't';
char *pt = &t;
char *px = malloc(10);
strcpy(px, pt);
printf("%d\n", pt > px);
Berdasarkan percobaan saya dengan kompiler saya, saya dituntun untuk berpikir bahwa setiap pointer dapat dibandingkan dengan pointer lainnya, 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.
Namun, saya bingung dengan apa yang saya baca di K&R.
Alasan saya bertanya adalah karena prof. sebenarnya membuatnya menjadi pertanyaan ujian. Dia memberi kode berikut:
struct A { char *p0; char *p1; }; int main(int argc, char **argv) { char a = 0; char *b = "W"; char c[] = [ 'L', 'O', 'L', 0 ]; struct A p[3]; p[0].p0 = &a; p[1].p0 = b; p[2].p0 = c; for(int i = 0; i < 3; i++) { p[i].p1 = malloc(10); strcpy(p[i].p1, p[i].p0); } }
Apa yang dievaluasi untuk:
p[0].p0 < p[0].p1
p[1].p0 < p[1].p1
p[2].p0 < p[2].p1
Jawabannya adalah 0
, 1
, dan 0
.
(Profesor saya tidak menyertakan penafian pada ujian bahwa pertanyaannya adalah untuk Ubuntu Linux 16.04, lingkungan pemrograman versi 64-bit)
(catatan editor: jika SO memperbolehkan lebih banyak tag, bagian terakhir itu akan menjamin x86-64 , linux , dan mungkin assembly . Jika inti dari pertanyaan / kelas adalah detail implementasi OS tingkat rendah secara khusus, daripada portable C.)
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).Jawaban:
Menurut standar C11 , operator relasional
<
,<=
,>
, dan>=
hanya dapat digunakan pada pointer ke elemen array yang sama atau objek struct. Ini dijabarkan dalam bagian 6.5.8p5:Perhatikan bahwa perbandingan apa pun yang tidak memenuhi persyaratan ini memicu perilaku yang tidak terdefinisi , artinya (di antara hal-hal lain) yang tidak dapat Anda andalkan bergantung pada hasil yang dapat diulang.
Dalam kasus khusus Anda, untuk perbandingan antara alamat dua variabel lokal dan antara alamat lokal dan alamat dinamis, operasi tampaknya "berfungsi", namun hasilnya dapat berubah dengan membuat perubahan yang tampaknya tidak terkait dengan kode Anda atau bahkan mengkompilasi kode yang sama dengan pengaturan optimasi yang berbeda. Dengan perilaku yang tidak terdefinisi, hanya karena kodenya bisa crash atau menghasilkan kesalahan tidak berarti itu akan terjadi .
Sebagai contoh, prosesor x86 yang berjalan dalam mode nyata 8086 memiliki model memori tersegmentasi menggunakan segmen 16-bit dan offset 16-bit untuk membangun alamat 20-bit. Jadi dalam hal ini alamat tidak mengonversi persis ke integer.
Operator kesetaraan
==
dan!=
namun tidak memiliki pembatasan ini. Mereka dapat digunakan antara dua pointer ke tipe yang kompatibel atau pointer NULL. Jadi menggunakan==
atau!=
dalam kedua contoh Anda akan menghasilkan kode C yang valid.Namun, bahkan dengan
==
dan!=
Anda bisa mendapatkan beberapa hasil yang tidak terduga namun masih terdefinisi dengan baik. Lihat Bisakah perbandingan kesetaraan pointer yang tidak terkait mengevaluasi kebenarannya? untuk detail lebih lanjut tentang ini.Mengenai pertanyaan ujian yang diberikan oleh profesor Anda, itu membuat sejumlah asumsi yang salah:
Jika Anda menjalankan kode ini pada arsitektur dan / atau dengan kompiler yang tidak memenuhi asumsi ini maka Anda bisa mendapatkan hasil yang sangat berbeda.
Juga, kedua contoh juga menunjukkan perilaku yang tidak terdefinisi ketika mereka memanggil
strcpy
, karena operan yang tepat (dalam beberapa kasus) menunjuk ke satu karakter dan bukan string yang diakhiri dengan nol, menghasilkan fungsi yang membaca melewati batas-batas variabel yang diberikan.sumber
<
antaramalloc
hasil dan variabel lokal (penyimpanan otomatis, yaitu tumpukan), ia dapat mengasumsikan bahwa jalur eksekusi tidak pernah diambil dan hanya mengkompilasi seluruh fungsi keud2
instruksi (menimbulkan ilegal -instruksi pengecualian yang akan ditangani oleh kernel dengan mengirimkan SIGILL ke proses). GCC / dentang melakukan ini dalam praktiknya untuk UB jenis lain, seperti jatuh dari kegagalanvoid
fungsi. godbolt.org turun sepertinya sekarang, tapi coba salin / tempelint foo(){int x=2;}
dan perhatikan kekuranganret
malloc
digunakan untuk mendapatkan lebih banyak memori dari OS, jadi tidak ada alasan untuk menganggap bahwa vars lokal Anda (tumpukan benang) di atasmalloc
dialokasikan secara dinamis penyimpanan.int x,y;
, implementasi ...Masalah utama dengan membandingkan pointer ke dua array berbeda dari tipe yang sama adalah bahwa array itu sendiri tidak perlu ditempatkan dalam posisi relatif tertentu - satu bisa berakhir sebelum dan sesudah yang lain.
Tidak, hasilnya tergantung pada implementasi dan faktor tak terduga lainnya.
Belum tentu ada setumpuk . Ketika ada, ia tidak perlu tumbuh. Itu bisa tumbuh. Ini bisa menjadi tidak berdampingan dalam beberapa cara yang aneh.
Mari kita lihat spesifikasi C , §6.5.8 di halaman 85 yang membahas operator relasional (yaitu operator perbandingan yang Anda gunakan). Perhatikan bahwa ini tidak berlaku untuk langsung
!=
atau==
perbandingan.Kalimat terakhir itu penting. Sementara saya memotong beberapa case yang tidak berhubungan untuk menghemat ruang, ada satu case yang penting bagi kami: dua array, bukan bagian dari struct / agregat objek 1 yang sama , dan kami membandingkan pointer ke dua array tersebut. Ini adalah perilaku yang tidak terdefinisi .
Sementara kompiler Anda baru saja memasukkan semacam instruksi mesin CMP (bandingkan) yang secara numerik membandingkan pointer, dan Anda beruntung di sini, UB adalah binatang yang sangat berbahaya. Secara harfiah apa pun bisa terjadi - kompiler Anda dapat mengoptimalkan seluruh fungsi termasuk efek samping yang terlihat. Itu bisa menelurkan setan hidung.
1 Pointer ke dalam dua array berbeda yang merupakan bagian dari struct yang sama dapat dibandingkan, karena ini berada di bawah klausa di mana dua array adalah bagian dari objek agregat yang sama (struct).
sumber
t
danx
didefinisikan dalam fungsi yang sama, tidak ada alasan untuk menganggap apa pun tentang bagaimana kompiler yang menargetkan x86-64 akan meletakkan penduduk lokal dalam bingkai tumpukan untuk fungsi ini. Tumpukan yang tumbuh ke bawah tidak ada hubungannya dengan urutan deklarasi variabel dalam satu fungsi. Bahkan dalam fungsi-fungsi yang terpisah, jika satu bisa sejalan dengan yang lain maka fungsi lokal "anak" masih bisa bercampur dengan orang tua.void
fungsi) g ++ dan clang ++ benar-benar melakukan itu dalam praktiknya: godbolt.org/z/g5vesB mereka berasumsi bahwa jalur eksekusi tidak diambil karena mengarah ke UB, dan kompilasi blok dasar seperti itu untuk instruksi ilegal. Atau tidak ada instruksi sama sekali, hanya diam-diam jatuh ke asm berikutnya apa pun jika fungsi itu pernah dipanggil. (Untuk beberapa alasangcc
tidak hanya melakukan inig++
).Pertanyaan-pertanyaan ini direduksi menjadi:
Dan jawaban untuk ketiganya adalah "implementasi didefinisikan". Pertanyaan prof Anda palsu; mereka mendasarkannya pada tata letak unix tradisional:
tetapi beberapa kesatuan modern (dan sistem alternatif) tidak sesuai dengan tradisi-tradisi itu. Kecuali mereka mengawali pertanyaan dengan "pada 1992"; pastikan untuk memberi -1 pada eval.
sumber
arr[]
merupakan objek yang demikian, Standar akan mengamanatkan bahwaarr+32768
membandingkan lebih besar daripadaarr
bahkan jika perbandingan pointer yang ditandatangani akan melaporkan sebaliknya.Pada hampir semua platform jarak jauh-modern, pointer dan integer memiliki hubungan pemesanan isomorfik, dan pointer ke objek terpisah tidak saling disisipkan. Sebagian besar penyusun mengekspos pemesanan ini kepada programmer ketika optimasi dinonaktifkan, tetapi Standar tidak membuat perbedaan antara platform yang memiliki pemesanan seperti itu dan yang tidak dan tidak mengharuskan implementasi apa pun mengekspos pemesanan seperti itu kepada programmer bahkan pada platform yang akan mendefinisikannya. Akibatnya, beberapa penulis kompiler melakukan berbagai macam optimisasi dan "optimisasi" berdasarkan pada asumsi bahwa kode tidak akan pernah membandingkan penggunaan operator relasional pada pointer ke objek yang berbeda.
Menurut Rationale yang diterbitkan, penulis Standar bermaksud bahwa implementasi memperluas bahasa dengan menentukan bagaimana mereka akan berperilaku dalam situasi Standar mencirikan sebagai "Perilaku Tidak Terdefinisi" (yaitu ketika Standar tidak memaksakan persyaratan ) ketika melakukan hal itu akan berguna dan praktis , tetapi beberapa penulis kompiler lebih suka berasumsi bahwa program tidak akan pernah mencoba mengambil manfaat dari apa pun di luar mandat Standar, daripada membiarkan program mengeksploitasi perilaku bermanfaat yang dapat didukung platform tanpa biaya tambahan.
Saya tidak mengetahui adanya kompiler yang dirancang secara komersial yang melakukan sesuatu yang aneh dengan perbandingan pointer, tetapi ketika kompiler pindah ke LLVM non-komersial untuk back end mereka, mereka semakin cenderung memproses kode yang tidak masuk akal yang perilakunya telah ditentukan sebelumnya. kompiler untuk platform mereka. Perilaku seperti itu tidak terbatas pada operator relasional, tetapi bahkan dapat mempengaruhi kesetaraan / ketidaksetaraan. Sebagai contoh, meskipun Standar menetapkan bahwa perbandingan antara pointer ke satu objek dan "just past" pointer ke objek yang sebelumnya akan membandingkan, kompiler berbasis gcc dan LLVM cenderung menghasilkan kode tidak masuk akal jika program melakukan seperti itu perbandingan.
Sebagai contoh situasi di mana bahkan perbandingan kesetaraan berperilaku tidak masuk akal dalam gcc dan dentang, pertimbangkan:
Baik dentang dan gcc akan menghasilkan kode yang akan selalu mengembalikan 4 bahkan jika
x
sepuluh elemen,y
segera mengikutinya, dani
nol menghasilkan perbandingan yang benar danp[0]
ditulis dengan nilai 1. Saya pikir yang terjadi adalah bahwa satu langkah optimasi penulisan ulang fungsi seolah-olah*p = 1;
diganti denganx[10] = 1;
. Kode yang terakhir akan menjadi setara jika kompiler diartikan*(x+10)
sebagai setara dengan*(y+i)
, tetapi sayangnya tahap optimasi hilir mengakui bahwa akses kex[10]
hanya akan didefinisikan jikax
memiliki setidaknya 11 elemen, yang akan membuat tidak mungkin untuk mempengaruhi aksesy
.Jika kompiler bisa mendapatkan "kreatif" dengan skenario kesetaraan pointer yang dijelaskan oleh Standar, saya tidak akan mempercayai mereka untuk menahan diri dari menjadi lebih kreatif dalam kasus di mana Standar tidak memaksakan persyaratan.
sumber
Ini sederhana: Membandingkan pointer tidak masuk akal karena lokasi memori untuk objek tidak pernah dijamin dalam urutan yang sama seperti yang Anda nyatakan. Pengecualian adalah array. & array [0] lebih rendah dari & array [1]. Itulah yang ditunjukkan oleh K&R. Dalam praktiknya, alamat anggota struct juga dalam urutan yang Anda nyatakan dalam pengalaman saya. Tidak ada jaminan untuk itu .... Pengecualian lain adalah jika Anda membandingkan pointer untuk yang sama. Ketika satu pointer sama dengan yang lain, Anda tahu itu menunjuk ke objek yang sama. Apapun itu. Pertanyaan ujian yang buruk jika Anda bertanya kepada saya. Bergantung pada Ubuntu Linux 16.04, lingkungan pemrograman versi 64-bit untuk pertanyaan ujian? Betulkah ?
sumber
arr[0]
,arr[1]
, dll secara terpisah. Anda mendeklarasikanarr
secara keseluruhan sehingga urutan elemen array individual adalah masalah yang berbeda dengan yang dijelaskan dalam pertanyaan ini.memcpy
untuk menyalin bagian yang berdekatan dari suatu struktur dan memengaruhi semua elemen di dalamnya dan tidak memengaruhi yang lain. Standar ceroboh tentang terminologi seperti apa aritmatika pointer dapat dilakukan dengan struktur ataumalloc()
penyimpanan yang dialokasikan. Theoffsetof
makro akan lebih berguna jika salah satu tidak bisa untuk jenis yang sama pointer aritmetika dengan byte dari struct sebagai denganchar[]
, tetapi Standard tidak tegas mengatakan bahwa byte struct (atau dapat digunakan sebagai) sebuah objek array.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:
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 ,
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 .
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.
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:
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 :
result
secara eksplisit demi kejelasan , dan mencetaknya untuk memaksa kompiler menghitung apa yang seharusnya menjadi kode mati yang mubazir.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:
Sementara pernyataan ini harus mengkompilasi dan menjalankan:
... sebagaimana mestinya:
... 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 :
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:
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
, atausbrk
. 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 belakangsbrk
; jika lebih jelas , sering , pada entitas yang lebih berbeda , lebih kritis - dan relevan juga pada platform di mana inimalloc
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
, dikompiler 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:
untuk ini:
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 alamata
, 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 itustrcpy
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 olehmalloc
.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 :
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:
Kalau ini tidak benar, pemrograman seperti yang kita tahu - dan menyukainya - tidak akan mungkin terjadi.
sumber
3.4.3
juga merupakan bagian yang harus Anda perhatikan: ini mendefinisikan UB sebagai perilaku "yang tidak ada persyaratan Standar Internasional ini ".C11 6.5.6/9
, dengan mengingat bahwa kata "wajib" menunjukkan persyaratanL "Ketika dua pointer dikurangi, keduanya akan menunjuk ke elemen dari objek array yang sama, atau satu melewati yang terakhir elemen dari objek array ".Pointer hanya bilangan bulat, seperti yang lainnya di komputer. Anda benar-benar dapat membandingkannya dengan
<
dan>
dan menghasilkan hasil tanpa menyebabkan program mogok. Yang mengatakan, standar tidak menjamin bahwa hasil tersebut memiliki arti luar perbandingan array.Dalam contoh Anda variabel alokasi stack, kompiler bebas untuk mengalokasikan variabel-variabel tersebut ke register atau alamat memori stack, dan dalam urutan apa pun itu pilih. Perbandingan seperti
<
dan>
karenanya tidak akan konsisten di seluruh kompiler atau arsitektur. Namun,==
dan!=
tidak begitu dibatasi, membandingkan kesetaraan pointer adalah operasi yang valid dan bermanfaat.sumber
int x[10],y[10],*p;
, jika kode mengevaluasiy[0]
, kemudian mengevaluasip>(x+5)
dan menulis*p
tanpa memodifikasip
untuk sementara, dan akhirnya mengevaluasiy[0]
lagi, ...(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
alih-alihisalpha()
karena implementasi waras apa yang membuat karakter-karakter itu terputus? Intinya adalah bahwa, bahkan jika tidak ada implementasi yang Anda tahu memiliki masalah, Anda harus membuat kode sebanyak mungkin jika Anda menghargai portabilitas. Saya sangat menghargai label "standar pakar", terima kasih untuk itu. Saya dapat memasukkan CV saya :-)