Mengapa array C tidak melacak panjangnya?


77

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:

  1. Memiliki panjang yang tersedia di buffer dapat mencegah buffer overrun.
  2. Gaya Java arr.lengthjelas dan menghindari programer dari harus mempertahankan banyak ints di stack jika berhadapan dengan beberapa array
  3. 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.


6
Saya kira itu bisa diperdebatkan, bahwa ketika C termasuk menggunakan struct sebagai parameter dan tipe nilai pengembalian, seharusnya sudah termasuk gula sintaksis untuk "vektor" (atau nama apa pun), yang di bawahnya akan struct dengan panjang dan baik array atau pointer ke array . Dukungan tingkat bahasa untuk konstruksi umum ini (juga ketika dilewatkan sebagai argumen terpisah dan bukan struct tunggal) akan menyimpan banyak bug dan perpustakaan standar yang disederhanakan juga.
hyde

3
Anda mungkin juga menemukan Mengapa Pascal Bukanlah Bahasa Pemrograman Favorit Saya Bagian 2.1 untuk berwawasan luas.

34
Sementara semua jawaban lain memiliki beberapa poin menarik, saya pikir intinya adalah bahwa C ditulis sehingga programmer bahasa assembly akan dapat menulis kode lebih mudah dan membuatnya portabel. Dengan mengingat hal itu, memiliki panjang array yang disimpan DENGAN sebuah array secara otomatis akan menjadi gangguan dan bukan kelemahan (seperti halnya keinginan-keinginan pelapis permen lainnya). Fitur-fitur ini kelihatannya bagus saat ini, tetapi saat itu sering kali sulit untuk memeras satu byte lagi dari program atau data ke dalam sistem Anda. Penggunaan memori yang sia-sia akan sangat membatasi adopsi C.
Dunk

6
Bagian sebenarnya dari jawaban Anda telah dijawab berkali-kali dengan cara yang saya inginkan, tetapi saya dapat mengekstraksi poin yang berbeda: "Mengapa ukuran malloc()area ed dapat diminta dengan cara portabel?" Itu adalah hal yang membuat saya bertanya-tanya beberapa kali.
glglgl

5
Voting untuk dibuka kembali. Ada beberapa alasan di suatu tempat, bahkan jika itu hanya "K&R tidak memikirkannya".
Telastyn

Jawaban:


106

Array C melacak panjangnya, karena panjang array adalah properti statis:

int xs[42];  /* a 42-element array */

Anda biasanya tidak dapat menanyakan panjang ini, tetapi Anda tidak perlu karena itu statis - cukup deklarasikan makro XS_LENGTHuntuk 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.


13
Saya pikir Anda bermaksud mengatakan, jika saya tidak salah, bahwa kompiler C melacak panjang array statis. Tapi ini tidak bagus untuk fungsi yang hanya mendapatkan pointer.
VF1

25
@ VF1 ya. Tapi yang penting hal adalah bahwa array dan pointer adalah hal yang berbeda di C . Dengan anggapan Anda tidak menggunakan ekstensi kompiler, Anda biasanya tidak bisa meneruskan array itu sendiri ke suatu fungsi, tetapi Anda bisa melewatkan sebuah pointer, dan mengindeks sebuah pointer seolah-olah itu adalah array. Anda secara efektif mengeluh bahwa pointer tidak memiliki panjang terpasang. Anda harus mengeluh bahwa array tidak dapat dilewatkan sebagai argumen fungsi, atau array menurun ke pointer secara implisit.
amon

37
"Anda biasanya tidak dapat menanyakan panjang ini" - sebenarnya Anda bisa, itu ukuran operator - sizeof (xs) akan mengembalikan 168 dengan asumsi int adalah empat byte panjang. Untuk mendapatkan 42, lakukan: sizeof (xs) / sizeof (int)
tcrosley

15
@tcrosley Itu hanya berfungsi dalam lingkup deklarasi array - coba lewati xs sebagai param ke fungsi lain lalu lihat berapa sizeof (xs) yang memberi Anda ...
Gwyn Evans

26
@GwynEvans lagi: pointer bukan array. Jadi jika Anda "meneruskan array sebagai param ke fungsi lain", Anda tidak melewatkan array melainkan sebagai pointer. Mengklaim bahwa di sizeof(xs)mana xsarray akan menjadi sesuatu yang berbeda di ruang lingkup lain adalah salah, karena desain C tidak memungkinkan array untuk meninggalkan ruang lingkup mereka. Jika di sizeof(xs)mana xsarray berbeda dari di sizeof(xs)mana xspointer, itu tidak mengherankan karena Anda membandingkan apel dengan jeruk .
amon

38

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:

Selain itu, bahasa (C) menunjukkan kekuatan yang cukup besar untuk menggambarkan konsep-konsep penting, misalnya, vektor yang panjangnya bervariasi pada waktu berjalan, dengan hanya beberapa aturan dasar dan konvensi. ... Sangat menarik untuk membandingkan pendekatan C dengan dua bahasa yang hampir bersamaan, Algol 68 dan Pascal [Jensen 74]. Array dalam Algol 68 memiliki batas tetap, atau `fleksibel: 'diperlukan mekanisme yang cukup baik dalam definisi bahasa, dan dalam kompiler, untuk mengakomodasi array fleksibel (dan tidak semua kompiler sepenuhnya mengimplementasikannya.) Pascal asli hanya memiliki ukuran tetap array dan string, dan ini terbukti mengurung [Kernighan 81].

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.


4
Ini cukup banyak kuku pertanyaan aslinya. Itu dan fakta bahwa C sedang sengaja dibuat "sentuhan ringan" ketika datang untuk memeriksa apa yang dilakukan programmer, sebagai bagian dari membuatnya menarik untuk menulis sistem operasi.
ClickRick

5
Tautan yang bagus, mereka juga secara eksplisit mengubah penyimpanan panjang string untuk menggunakan pembatas 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 :-)
Voo

5
Array yang tidak dikontrol juga cocok dengan pendekatan bare metal dari C. Ingatlah bahwa buku K&R C kurang dari 300 halaman dengan tutorial bahasa, referensi, dan daftar panggilan standar. Buku O'Reilly Regex saya hampir dua kali lebih panjang dari K&R C.
Michael Shopsin

22

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:

String x = "hello";
if (strcmp(x, "hello") == 0) 
  exit;

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.


10
Memeriksa batas juga membutuhkan waktu. Sepele dalam istilah hari ini, tetapi sesuatu yang orang perhatikan ketika mereka peduli sekitar 4 byte.
Gort the Robot

18
@StevenBurnap: tidak sepele bahkan hari ini jika Anda berada di loop dalam yang melampaui setiap piksel dari gambar 200 MB. Secara umum, jika Anda menulis C, Anda ingin cepat , dan Anda tidak ingin membuang waktu dalam cek terikat yang tidak berguna di setiap iterasi ketika forloop Anda sudah diatur untuk menghormati batas-batas.
Matteo Italia

4
@ VF1 "kembali pada hari" itu bisa saja dua byte (DEC PDP / 11 siapa pun?)
ClickRick

7
Bukan hanya "kembali pada hari". Untuk perangkat lunak yang C ditargetkan sebagai "bahasa rakitan portabel" seperti kernel OS, driver perangkat, perangkat lunak real time tertanam dll. membuang-buang setengah lusin instruksi untuk memeriksa batas itu penting, dan, dalam banyak kasus, Anda harus "di luar batas" (bagaimana Anda bisa menulis debugger jika Anda tidak bisa secara acak mengakses penyimpanan program lain?).
James Anderson

3
Ini sebenarnya argumen yang agak lemah mengingat BCPL telah lama menghitung argumen. Sama seperti Pascal meskipun itu terbatas pada 1 kata sehingga umumnya 8 atau 9 bit saja, yang agak membatasi (itu juga menghalangi kemungkinan untuk berbagi bagian dari string, meskipun optimasi itu mungkin terlalu maju untuk saat itu). Dan mendeklarasikan string sebagai struct dengan panjang yang diikuti oleh array benar-benar tidak akan memerlukan dukungan kompiler khusus ..
Voo

11

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.


6
"Desainnya akan berbeda" bukan alasan mengapa desainnya berbeda.
VF1

7
@ VF1: Apakah Anda pernah memprogram dalam Pascal Standar? Kemampuan C untuk menjadi cukup fleksibel dengan susunan adalah peningkatan besar pada perakitan (tidak ada keselamatan apa pun) dan generasi pertama dari bahasa yang aman menggunakan
huruf

5
Kemampuan untuk mengiris array memang merupakan argumen besar untuk desain C89.

Peretas Fortran lama juga dapat menggunakan properti ini dengan baik (meskipun, ia harus meneruskan slice ke array di Fortran). Membingungkan dan menyakitkan untuk diprogram atau didebug, tetapi cepat dan elegan saat bekerja.
dmckee

3
Ada satu alternatif desain menarik yang memungkinkan pengirisan: Jangan menyimpan panjang di samping array. Untuk sembarang pointer ke array, simpan panjangnya dengan pointer. (Ketika Anda hanya memiliki array C nyata, ukurannya adalah konstanta waktu kompilasi dan tersedia untuk kompiler.) Dibutuhkan lebih banyak ruang, tetapi memungkinkan pemotongan sambil mempertahankan panjangnya. Karat melakukan ini untuk &[T]jenis, misalnya.

8

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:

void ClearTwoElements(int *ptr)
{
  ptr[-2] = 0;
  ptr[2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo+2);
  ClearTwoElements(foo+7);
  ClearTwoElements(foo+1);
  ClearTwoElements(foo+8);
}

satu-satunya cara agar kode dapat menerima panggilan pertama, ClearTwoElementstetapi menolak panggilan kedua adalah ClearTwoElementsmetode menerima informasi yang cukup untuk mengetahui bahwa dalam setiap kasus kode menerima referensi ke bagian array fooselain 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 untuk ClearTwoElementskemungkinan akan menjadi sesuatu seperti:

void ClearTwoElements(int *ptr)
{
  int* array_end = ARRAY_END(ptr);
  if ((array_end - ARRAY_BASE(ptr)) < 10 ||
      (ARRAY_BASE(ptr)+4) <= ADDRESS(ptr) ||          
      (array_end - 4) < ADDRESS(ptr)))
    trap();
  *(ADDRESS(ptr) - 4) = 0;
  *(ADDRESS(ptr) + 4) = 0;
}

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:

void ClearTwoElements(int arr[], int index)
{
  arr[index-2] = 0;
  arr[index+2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo,2);
  ClearTwoElements(foo,7);
  ClearTwoElements(foo,1);
  ClearTwoElements(foo,8);
}

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.


Jika array memiliki ukuran runtime, maka pointer ke array akan berbeda secara fundamental dari pointer ke elemen array. Terakhir mungkin tidak dapat langsung dikonversi menjadi yang pertama (tanpa membuat larik baru). []sintaksis mungkin masih ada untuk pointer, tetapi akan berbeda dari untuk array "nyata" hipotetis ini, dan masalah yang Anda jelaskan mungkin tidak ada.
hyde

@ Hyde: Pertanyaannya adalah apakah aritmatika harus diizinkan pada pointer yang alamat basis objeknya tidak diketahui. Juga, saya lupa kesulitan lain: array dalam struktur. Memikirkan hal itu, saya tidak yakin akan ada jenis pointer yang dapat menunjuk ke array yang disimpan dalam struktur, tanpa memerlukan setiap pointer untuk memasukkan tidak hanya alamat pointer itu sendiri, tetapi juga hukum atas dan bawah rentang yang dapat diakses.
supercat

Titik menarik. Saya pikir ini masih mengurangi jawaban amon.
VF1

Pertanyaannya bertanya tentang array. Pointer adalah alamat memori dan tidak akan berubah dengan premis pertanyaan, sejauh memahami maksudnya. Array akan mendapatkan panjang, pointer tidak akan berubah (kecuali pointer ke array perlu menjadi yang baru, berbeda, tipe unik, seperti pointer ke struct).
hyde

@hyde: Jika seseorang cukup mengubah semantik bahasa, mungkin saja array memiliki panjang yang terkait, meskipun array yang disimpan dalam struktur akan menimbulkan beberapa kesulitan. Dengan semantik sebagaimana adanya, pemeriksaan batas array hanya akan berguna jika pemeriksaan yang sama diterapkan pada pointer ke elemen array.
supercat

7

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.


3
C tidak terlalu "dirancang" karena berevolusi. Awalnya, pernyataan seperti int f[5];tidak akan dibuat fsebagai array lima item; sebaliknya, itu setara dengan int 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 bahwa fpernah ada hubungannya dengan array. Perilaku tipe array yang tidak konsisten berasal dari ini.
supercat

1
Ternyata tidak ada programmer yang tahu apa yang mereka lakukan sampai tingkat yang dibutuhkan C.
CodesInChaos

7

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:

retval = my_func(my_array, my_array_length);

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.


1
+1 "Dan jika semua kode yang Anda tulis sudah tahu panjang array, Anda tidak perlu memberikan panjangnya sebagai variabel sama sekali."
林果 皞

Jika hanya pointer pointer + panjang yang telah dimasukkan ke dalam bahasa dan pustaka standar. Begitu banyak lubang keamanan yang bisa dihindari.
CodesInChaos

Maka tidak akan benar-benar menjadi C. Ada bahasa lain yang melakukan itu. C membuat Anda level rendah.
thomasrutter

C diciptakan sebagai bahasa pemrograman tingkat rendah, dan banyak dialek masih mendukung pemrograman tingkat rendah, tetapi banyak penulis kompiler menyukai dialek yang tidak dapat benar-benar disebut bahasa tingkat rendah. Mereka mengizinkan dan bahkan memerlukan sintaks tingkat rendah, tetapi kemudian mencoba untuk menyimpulkan konstruksi tingkat tinggi yang perilakunya mungkin tidak cocok dengan semantik yang tersirat oleh sintaksis.
supercat

5

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?


1
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?
Mahdi

Jika saya telah mendesainnya, argumen fungsi yang ditulis 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.
CodesInChaos

1

Dari Perkembangan Bahasa C :

Struktur, tampaknya, harus memetakan dengan cara yang intuitif ke memori di dalam mesin, tetapi dalam struktur yang mengandung array, tidak ada tempat yang baik untuk menyimpan pointer yang berisi basis array, maupun cara mudah untuk mengaturnya. diinisialisasi. Sebagai contoh, entri direktori sistem Unix awal dapat dijelaskan dalam C as
struct {
    int inumber;
    char    name[14];
};
Saya ingin struktur tidak hanya untuk mencirikan objek abstrak tetapi juga untuk menggambarkan kumpulan bit yang dapat dibaca dari direktori. Di mana kompiler dapat menyembunyikan pointer ke namesemantik yang diminta? Bahkan jika struktur dianggap lebih abstrak, dan ruang untuk pointer bisa disembunyikan entah bagaimana, bagaimana saya bisa menangani masalah teknis menginisialisasi pointer ini dengan benar ketika mengalokasikan objek yang rumit, mungkin salah satu yang menetapkan struktur berisi array berisi struktur hingga kedalaman sewenang-wenang?

Solusinya merupakan lompatan penting dalam rantai evolusi antara BCPL tanpa tipe dan mengetik C. Ini menghilangkan materialisasi dari pointer di penyimpanan, dan bukannya menyebabkan penciptaan pointer ketika nama array disebutkan dalam ekspresi. Aturan, yang bertahan dalam C hari ini, adalah bahwa nilai-nilai tipe array dikonversi, ketika mereka muncul dalam ekspresi, menjadi pointer ke yang pertama dari objek yang membentuk array.

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

T *p = &a[0][0];

for ( size_t i = 0; i < rows; i++ )
  for ( size_t j = 0; j < cols; j++ )
    do_something_with( *p++ );

-2

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.

char src[] = "Hello, world";
char dst[1024];
int *my_array = src; /* What? Compiler warning, but the code is valid. */
int *other_array = dst;
int i;
for (i = 0; i <= sizeof(src)/sizeof(int); i++)
    other_array[i] = my_array[i]; /* Oh well, we've copied some extra bytes */
printf("%s\n", dst);

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.


2
Saya tidak berpikir Anda telah menjawab pertanyaan itu.
Robert Harvey

2
Apa yang Anda katakan itu benar, tetapi orang yang bertanya ingin tahu mengapa ini terjadi.

9
Ingat, salah satu nama panggilan untuk C adalah "rakitan portabel." Sementara versi yang lebih baru dari standar telah menambahkan konsep tingkat yang lebih tinggi, pada intinya, itu terdiri dari konstruksi tingkat rendah sederhana dan instruksi yang umum di sebagian besar mesin non-sepele. Ini mendorong sebagian besar keputusan desain yang dibuat dalam bahasa tersebut. Satu-satunya variabel yang ada saat runtime adalah integer, float, dan pointer. Instruksi termasuk aritmatika, perbandingan, dan lompatan. Yang lainnya adalah lapisan tipis yang dibangun di atasnya.

8
Itu salah untuk mengatakan C tidak memiliki array, mengingat bagaimana Anda benar-benar tidak dapat menghasilkan biner yang sama dengan konstruksi lainnya (well, setidaknya tidak jika Anda mempertimbangkan penggunaan #defines untuk menentukan ukuran array). Array dalam C adalah "urutan data kontinu", tidak ada yang manis tentang hal itu. Menggunakan pointer seperti mereka array adalah gula sintaksis di sini (bukan aritmatika pointer eksplisit), bukan array sendiri.
hyde

2
Ya, pertimbangkan kode ini: struct Foo { int arr[10]; }. arradalah array, bukan pointer.
Gort the Robot
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.