Apa alasan di balik tidak secara eksplisit menyimpan panjang array dengan array C
?
Cara saya melihatnya, ada banyak alasan untuk melakukannya tetapi tidak terlalu banyak mendukung standar (C89). Misalnya:
- Memiliki panjang yang tersedia di buffer dapat mencegah buffer overrun.
- Gaya Java
arr.length
jelas dan menghindari programer dari harus mempertahankan banyakint
s di stack jika berhadapan dengan beberapa array - Parameter fungsi menjadi lebih meyakinkan.
Tapi mungkin alasan yang paling memotivasi, menurut pendapat saya, adalah bahwa biasanya, tidak ada ruang yang disimpan tanpa mempertahankan panjangnya. Saya berani mengatakan bahwa sebagian besar penggunaan array melibatkan alokasi dinamis. Benar, mungkin ada beberapa kasus di mana orang menggunakan array yang dialokasikan pada stack, tapi itu hanya satu panggilan fungsi * - stack dapat menangani tambahan 4 atau 8 byte.
Karena manajer tumpukan harus melacak ukuran blok bebas yang digunakan oleh array yang dialokasikan secara dinamis, mengapa tidak membuat informasi tersebut dapat digunakan (dan menambahkan aturan tambahan, diperiksa pada waktu kompilasi, bahwa seseorang tidak dapat memanipulasi panjang secara eksplisit kecuali jika mau suka menembak diri sendiri di kaki).
Satu-satunya hal yang dapat saya pikirkan di sisi lain adalah bahwa tidak ada pelacakan panjang mungkin telah membuat kompiler sederhana, tapi tidak yang jauh lebih sederhana.
* Secara teknis, seseorang dapat menulis semacam fungsi rekursif dengan array dengan penyimpanan otomatis, dan dalam hal ini (sangat rumit) menyimpan panjang mungkin memang menghasilkan lebih banyak penggunaan ruang secara efektif.
malloc()
area ed dapat diminta dengan cara portabel?" Itu adalah hal yang membuat saya bertanya-tanya beberapa kali.Jawaban:
Array C melacak panjangnya, karena panjang array adalah properti statis:
Anda biasanya tidak dapat menanyakan panjang ini, tetapi Anda tidak perlu karena itu statis - cukup deklarasikan makro
XS_LENGTH
untuk panjangnya, dan Anda selesai.Masalah yang lebih penting adalah bahwa array C secara implisit terdegradasi menjadi pointer, misalnya ketika diteruskan ke suatu fungsi. Ini memang masuk akal, dan memungkinkan untuk beberapa trik tingkat rendah yang bagus, tetapi kehilangan informasi tentang panjang array. Jadi pertanyaan yang lebih baik adalah mengapa C dirancang dengan degradasi implisit ke pointer.
Masalah lain adalah bahwa pointer tidak memerlukan penyimpanan kecuali alamat memori itu sendiri. C memungkinkan kita untuk melemparkan bilangan bulat ke pointer, pointer ke pointer lainnya, dan memperlakukan pointer seolah-olah mereka array. Saat melakukan ini, C tidak cukup gila untuk membuat beberapa panjang array menjadi ada, tetapi tampaknya percaya pada moto Spiderman: dengan kekuatan besar programmer diharapkan akan memenuhi tanggung jawab besar untuk melacak panjang dan meluap.
sumber
sizeof(xs)
manaxs
array akan menjadi sesuatu yang berbeda di ruang lingkup lain adalah salah, karena desain C tidak memungkinkan array untuk meninggalkan ruang lingkup mereka. Jika disizeof(xs)
manaxs
array berbeda dari disizeof(xs)
manaxs
pointer, itu tidak mengherankan karena Anda membandingkan apel dengan jeruk .Banyak dari ini berkaitan dengan komputer yang tersedia pada saat itu. Tidak hanya program yang dikompilasi harus dijalankan pada komputer sumber daya yang terbatas, tetapi, mungkin yang lebih penting, kompiler itu sendiri harus dijalankan pada mesin ini. Pada saat Thompson mengembangkan C, ia menggunakan PDP-7, dengan 8k RAM. Fitur bahasa yang kompleks yang tidak memiliki analog langsung pada kode mesin yang sebenarnya sama sekali tidak termasuk dalam bahasa.
Pembacaan yang cermat melalui sejarah C menghasilkan lebih banyak pemahaman di atas, tetapi itu tidak sepenuhnya akibat dari keterbatasan mesin yang mereka miliki:
Array C secara inheren lebih kuat. Menambahkan batasan pada mereka membatasi apa yang bisa digunakan oleh programmer. Pembatasan seperti itu mungkin berguna untuk programmer, tetapi tentu juga membatasi.
sumber
to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator
- sangat banyak untuk itu :-)Kembali pada hari ketika C dibuat, dan tambahan 4 byte ruang untuk setiap string tidak peduli seberapa pendek akan sangat sia-sia!
Ada masalah lain - ingat bahwa C tidak berorientasi objek, jadi jika Anda melakukan awalan panjang semua string, itu harus didefinisikan sebagai tipe intrinsik kompiler, bukan a
char*
. Jika itu adalah tipe khusus, maka Anda tidak akan dapat membandingkan string dengan string konstan, yaitu:harus memiliki detail kompiler khusus untuk mengonversi string statis menjadi String, atau memiliki fungsi string yang berbeda untuk memperhitungkan awalan panjang.
Saya pikir pada akhirnya, mereka tidak memilih cara awalan panjang seperti Pascal.
sumber
for
loop Anda sudah diatur untuk menghormati batas-batas.Dalam C, setiap himpunan bagian yang berdekatan dari array juga merupakan array dan dapat dioperasikan seperti itu. Ini berlaku untuk operasi baca dan tulis. Properti ini tidak akan tahan jika ukurannya disimpan secara eksplisit.
sumber
&[T]
jenis, misalnya.Masalah terbesar dengan memiliki array yang ditandai dengan panjangnya tidak begitu banyak ruang yang dibutuhkan untuk menyimpan panjang itu, atau pertanyaan tentang bagaimana harus disimpan (menggunakan satu byte tambahan untuk array pendek umumnya tidak akan keberatan, juga tidak akan menggunakan empat byte tambahan untuk array panjang, tetapi menggunakan empat byte bahkan untuk array pendek mungkin). Masalah yang jauh lebih besar adalah kode yang diberikan seperti:
satu-satunya cara agar kode dapat menerima panggilan pertama,
ClearTwoElements
tetapi menolak panggilan kedua adalahClearTwoElements
metode menerima informasi yang cukup untuk mengetahui bahwa dalam setiap kasus kode menerima referensi ke bagian arrayfoo
selain mengetahui bagian mana. Itu biasanya akan menggandakan biaya melewati parameter pointer. Lebih lanjut, jika setiap array didahului oleh pointer ke alamat yang baru saja melewati akhir (format yang paling efisien untuk validasi), kode yang dioptimalkan untukClearTwoElements
kemungkinan akan menjadi sesuatu seperti:Perhatikan bahwa pemanggil metode dapat, secara umum, secara sempurna secara sah mengirimkan pointer ke awal array atau elemen terakhir ke suatu metode; hanya jika metode ini mencoba untuk mengakses elemen-elemen yang keluar di luar array yang lewat akan pointer tersebut menyebabkan masalah. Akibatnya, metode yang dipanggil harus terlebih dahulu memastikan array cukup besar sehingga aritmatika pointer untuk memvalidasi argumennya tidak akan keluar dari batas, dan kemudian melakukan beberapa perhitungan pointer untuk memvalidasi argumen. Waktu yang dihabiskan dalam validasi tersebut kemungkinan akan melebihi biaya yang dihabiskan untuk melakukan pekerjaan nyata. Lebih lanjut, metode ini mungkin bisa lebih efisien jika ditulis dan dipanggil:
Konsep tipe yang menggabungkan sesuatu untuk mengidentifikasi objek dengan sesuatu untuk mengidentifikasi bagiannya adalah bagus. Akan tetapi, penunjuk gaya-C lebih cepat jika tidak perlu melakukan validasi.
sumber
[]
sintaksis mungkin masih ada untuk pointer, tetapi akan berbeda dari untuk array "nyata" hipotetis ini, dan masalah yang Anda jelaskan mungkin tidak ada.Salah satu perbedaan mendasar antara C dan sebagian besar bahasa generasi ke-3 lainnya, dan semua bahasa yang lebih baru yang saya ketahui, adalah bahwa C tidak dirancang untuk membuat hidup lebih mudah atau lebih aman bagi programmer. Itu dirancang dengan harapan bahwa programmer tahu apa yang mereka lakukan dan ingin melakukan dengan tepat dan hanya itu. Itu tidak melakukan apa pun 'di balik layar' sehingga Anda tidak mendapatkan kejutan. Bahkan optimasi tingkat kompiler adalah opsional (kecuali jika Anda menggunakan kompiler Microsoft).
Jika seorang programmer ingin menulis batasan memeriksa kode mereka, C membuatnya cukup sederhana untuk melakukannya, tetapi programmer harus memilih untuk membayar harga yang sesuai dalam hal ruang, kompleksitas dan kinerja. Meskipun saya belum menggunakannya dalam kemarahan selama bertahun-tahun, saya masih menggunakannya ketika mengajar pemrograman untuk melintasi konsep pengambilan keputusan berdasarkan kendala. Pada dasarnya, itu berarti Anda dapat memilih untuk melakukan apa pun yang Anda inginkan, tetapi setiap keputusan yang Anda buat memiliki harga yang harus Anda waspadai. Ini menjadi lebih penting ketika Anda mulai memberi tahu orang lain apa yang Anda ingin program mereka lakukan.
sumber
int f[5];
tidak akan dibuatf
sebagai array lima item; sebaliknya, itu setara denganint CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;
. Deklarasi sebelumnya dapat diproses tanpa kompiler harus benar-benar "memahami" waktu array; itu hanya harus mengeluarkan direktif assembler untuk mengalokasikan ruang dan kemudian bisa lupa bahwaf
pernah ada hubungannya dengan array. Perilaku tipe array yang tidak konsisten berasal dari ini.Jawaban singkat:
Karena C adalah bahasa pemrograman tingkat rendah, bahasa C mengharapkan Anda untuk mengurus sendiri masalah ini, tetapi ini menambah fleksibilitas yang lebih besar dalam cara Anda menerapkannya.
C memiliki konsep waktu kompilasi array yang diinisialisasi dengan panjang tetapi pada saat runtime semuanya disimpan sebagai pointer tunggal ke awal data. Jika Anda ingin meneruskan panjang array ke suatu fungsi bersama dengan array, Anda melakukannya sendiri:
Atau Anda bisa menggunakan struct dengan pointer dan panjang, atau solusi lain.
Bahasa tingkat yang lebih tinggi akan melakukan ini untuk Anda sebagai bagian dari jenis arraynya. Dalam C Anda diberi tanggung jawab untuk melakukan ini sendiri, tetapi juga fleksibilitas untuk memilih bagaimana melakukannya. Dan jika semua kode yang Anda tulis sudah tahu panjang array, Anda tidak perlu melewatkan panjang sebagai variabel sama sekali.
Kelemahan yang jelas adalah bahwa tanpa batas yang melekat memeriksa array yang dilewati sebagai pointer Anda dapat membuat beberapa kode berbahaya tapi itu adalah sifat bahasa tingkat rendah / sistem dan pertukaran yang mereka berikan.
sumber
Masalah penyimpanan tambahan adalah masalah, tetapi menurut saya kecil. Lagi pula, sebagian besar waktu Anda akan perlu untuk melacak panjangnya, meskipun amon membuat poin yang baik bahwa sering dapat dilacak secara statis.
Masalah yang lebih besar adalah di mana menyimpan panjang dan berapa lama membuatnya. Tidak ada satu tempat yang berfungsi dalam semua situasi. Anda mungkin mengatakan hanya menyimpan panjang dalam memori sebelum data. Bagaimana jika array tidak menunjuk ke memori, tetapi sesuatu seperti buffer UART?
Meninggalkan panjang memungkinkan programmer untuk membuat abstraksi sendiri untuk situasi yang sesuai, dan ada banyak perpustakaan siap pakai yang tersedia untuk kasus tujuan umum. Pertanyaan sebenarnya adalah mengapa abstraksi itu tidak digunakan dalam aplikasi yang sensitif terhadap keamanan?
sumber
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?
Bisakah Anda jelaskan ini sedikit lebih banyak? Juga sesuatu yang mungkin terjadi terlalu sering atau itu hanya kasus langka?T[]
tidak akan setara dengan,T*
tetapi meneruskan tuple pointer dan ukuran ke fungsi. Array ukuran tetap dapat meluruh ke irisan array seperti itu, alih-alih membusuk ke pointer seperti yang mereka lakukan dalam C. Keuntungan utama dari pendekatan ini bukanlah bahwa itu aman dengan sendirinya, tapi itu adalah konvensi di mana segala sesuatu, termasuk perpustakaan standar dapat membangun.Dari Perkembangan Bahasa C :
Bagian itu membahas mengapa ekspresi array membusuk ke pointer di sebagian besar keadaan, tetapi alasan yang sama berlaku untuk mengapa panjang array tidak disimpan dengan array itu sendiri; jika Anda ingin pemetaan satu-ke-satu antara definisi tipe dan perwakilannya dalam memori (seperti yang dilakukan Ritchie), maka tidak ada tempat yang baik untuk menyimpan metadata itu.
Juga, pikirkan tentang array multidimensi; di mana Anda menyimpan metadata panjang untuk setiap dimensi sehingga Anda masih bisa berjalan melalui array dengan sesuatu seperti
sumber
Pertanyaannya mengasumsikan bahwa ada array dalam C. Tidak ada. Hal-hal yang disebut array hanyalah gula sintaksis untuk operasi pada urutan data dan aritmatika pointer terus menerus.
Kode berikut menyalin beberapa data dari src ke dst dalam potongan berukuran int tidak mengetahui bahwa itu sebenarnya string karakter.
Mengapa C sangat sederhana sehingga tidak memiliki array yang tepat? Saya tidak tahu jawaban yang benar untuk pertanyaan baru ini. Tetapi beberapa orang sering mengatakan bahwa C hanya (agak) assembler lebih mudah dibaca dan portabel.
sumber
struct Foo { int arr[10]; }
.arr
adalah array, bukan pointer.