Mengapa panjang array C tidak bisa 0?

13

Standar C11 mengatakan array, baik ukuran dan panjang variabel "harus memiliki nilai lebih besar dari nol." Apa justifikasi untuk tidak mengizinkan panjang 0?

Khusus untuk array panjang variabel, sangat masuk akal untuk memiliki ukuran nol setiap sesekali. Ini juga berguna untuk array statis ketika ukurannya dari opsi konfigurasi makro atau membangun.

Menariknya GCC (dan dentang) menyediakan ekstensi yang memungkinkan array panjang nol. Java juga memungkinkan array dengan panjang nol.

Kevin Cox
sumber
7
stackoverflow.com/q/8625572 ... "Array dengan panjang nol akan menjadi rumit dan membingungkan untuk diselaraskan dengan persyaratan bahwa setiap objek memiliki alamat unik."
Robert Harvey
3
@RobertHarvey: Diberikan struct { int p[1],q[1]; } foo; int *pp = p+1;, ppakan menjadi pointer yang sah, tetapi *pptidak akan memiliki alamat yang unik. Mengapa logika yang sama tidak dapat berlaku dengan array nol panjang? Katakan bahwa diberikan int q[0]; dalam struktur , qakan merujuk ke alamat yang validitasnya akan seperti yang ada pada p+1contoh di atas.
supercat
@DocBrown Dari standar C11 6.7.6.2.5 berbicara tentang ekspresi yang digunakan untuk menentukan ukuran VLA "... setiap kali dievaluasi ia akan memiliki nilai lebih besar dari nol." Saya tidak tahu tentang C99 (dan tampaknya aneh bahwa mereka akan mengubahnya) tetapi sepertinya Anda tidak dapat memiliki panjang nol.
Kevin Cox
@KevinCox: apakah ada versi online gratis dari standar C11 (atau bagian yang dimaksud) yang tersedia?
Doc Brown
Versi terakhir tidak tersedia secara gratis (sayang sekali) tetapi Anda dapat mengunduh konsep. Draf terakhir yang tersedia adalah open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf .
Kevin Cox

Jawaban:

11

Masalah yang akan saya pertaruhkan adalah bahwa array C hanyalah pointer ke awal alokasi memori yang dialokasikan. Memiliki ukuran 0 berarti Anda memiliki pointer ke ... tidak ada? Anda tidak dapat memiliki apa-apa, jadi harus ada sesuatu yang sewenang-wenang yang dipilih. Anda tidak dapat menggunakan null, karena dengan demikian array 0 panjang Anda akan terlihat seperti pointer nol. Dan pada saat itu setiap implementasi yang berbeda akan memilih perilaku sewenang-wenang yang berbeda, yang menyebabkan kekacauan.

Telastyn
sumber
7
@ Delnan: Yah, jika Anda ingin menjadi pedantic tentang hal itu, array dan aritmatika pointer didefinisikan sedemikian rupa sehingga pointer dapat dengan mudah digunakan untuk mengakses array atau untuk mensimulasikan array. Dengan kata lain, itu pointer aritmatika dan pengindeksan array yang setara dalam C. Tapi hasilnya tetap sama ... jika panjang array adalah nol, Anda masih menunjuk ke nol.
Robert Harvey
3
@RobertHarvey Semua benar, tetapi kata-kata penutup Anda (dan seluruh jawaban dalam retrospeksi) hanya tampak seperti cara yang membingungkan dan membingungkan untuk menjelaskan array seperti itu (saya pikir itulah jawaban ini disebut "sepotong memori yang dialokasikan"?) Akan memiliki sizeof0, dan bagaimana itu akan menyebabkan masalah. Semua itu bisa dijelaskan dengan menggunakan konsep dan terminologi yang tepat tanpa kehilangan kejelasan atau kejelasan. Mencampur array dan pointer hanya berisiko menyebarkan array = pointer kesalahpahaman (yang lebih penting dalam konteks lain) tanpa manfaat.
2
" Anda tidak dapat menggunakan null, karena dengan begitu 0 array panjang Anda akan terlihat seperti null pointer " - sebenarnya itulah yang dilakukan Delphi. Dynarrays kosong dan tali panjang kosong secara teknis adalah pointer nol.
JensG
3
-1, saya kenyang dengan @delnan di sini. Ini tidak menjelaskan apa-apa, terutama dalam konteks apa yang OP tulis tentang beberapa kompiler utama yang mendukung konsep array panjang nol. Saya cukup yakin array panjang nol dapat disediakan dalam C dengan cara implementasi-independen, tidak "mengarah ke kekacauan".
Doc Brown
6

Mari kita lihat bagaimana sebuah array biasanya diletakkan dalam memori:

         +----+
arr[0] : |    |
         +----+
arr[1] : |    |
         +----+
arr[2] : |    |
         +----+
          ...
         +----+
arr[n] : |    |
         +----+

Perhatikan bahwa tidak ada objek terpisah yang bernama arryang menyimpan alamat elemen pertama; ketika array muncul dalam ekspresi, C menghitung alamat elemen pertama sesuai kebutuhan.

Jadi, mari kita berpikir tentang hal ini: array 0-elemen akan memiliki tidak ada penyimpanan set samping untuk itu, berarti tidak ada yang menghitung alamat array yang dari (dengan kata lain, tidak ada objek pemetaan untuk pengenal). Itu seperti mengatakan, "Saya ingin membuat intvariabel yang tidak menggunakan memori." Ini operasi yang tidak masuk akal.

Edit

Array Java adalah hewan yang sepenuhnya berbeda dari array C dan C ++; mereka bukan tipe primitif, tetapi tipe referensi yang berasal Object.

Edit 2

Poin yang muncul dalam komentar di bawah ini - batasan "lebih besar dari 0" hanya berlaku untuk array di mana ukurannya ditentukan melalui ekspresi konstan ; sebuah VLA dibolehkan memiliki panjang 0 Mendeklarasikan VLA dengan ekspresi non-konstan bernilai 0 bukanlah pelanggaran kendala, tetapi itu memunculkan perilaku yang tidak terdefinisi.

Sudah jelas bahwa VLA adalah hewan yang berbeda dari array biasa , dan implementasinya dapat memungkinkan untuk ukuran 0 . Mereka tidak dapat dideklarasikan staticatau pada lingkup file, karena ukuran objek tersebut harus diketahui sebelum program dimulai.

Juga tidak ada artinya bahwa pada C11, implementasi tidak diperlukan untuk mendukung VLA.

John Bode
sumber
3
Maaf, tapi IMHO Anda melewatkan intinya, sama seperti Telastyn. Array nol panjang dapat membuat banyak akal, dan implementasi yang ada seperti yang OP katakan kepada kami tentang menunjukkan bahwa itu bisa dilakukan.
Doc Brown
@DocBrown: Pertama, saya membahas mengapa standar bahasa kemungkinan besar tidak mengizinkan mereka. Kedua, saya ingin contoh di mana array 0-length masuk akal, karena saya jujur ​​tidak bisa memikirkan satu. Implementasi yang paling mungkin adalah memperlakukan T a[0]sebagai T *a, tetapi mengapa tidak menggunakan saja T *a?
John Bode
Maaf, tapi saya tidak membeli "alasan teoretis" mengapa standar melarang ini. Baca jawaban saya bagaimana alamat tersebut dapat dihitung dengan mudah. Dan saya sarankan Anda mengikuti tautan dalam komentar pertama Robert Harveys di bawah pertanyaan dan membaca jawaban kedua, ada contoh yang berguna.
Doc Brown
@DocBrown: Ah. The structhack. Saya tidak pernah menggunakannya secara pribadi; tidak pernah bekerja pada masalah yang membutuhkan structtipe berukuran bervariasi .
John Bode
2
Dan jangan lupa AFAIK sejak C99, C memungkinkan array panjang variabel. Dan ketika ukuran array adalah parameter, tidak harus memperlakukan nilai 0 sebagai kasus khusus dapat membuat banyak program lebih sederhana.
Doc Brown
2

Anda biasanya ingin array ukuran nol (pada kenyataannya variabel) Anda mengetahui ukurannya pada saat dijalankan. Kemudian kemas dalam structdan gunakan anggota array fleksibel , seperti misalnya:

struct my_st {
   unsigned len;
   double flexarray[]; // of size len
};

Tentunya anggota array fleksibel harus menjadi yang terakhir structdan Anda perlu memiliki sesuatu sebelumnya. Seringkali itu akan menjadi sesuatu yang terkait dengan panjang runtime yang ditempati anggota array fleksibel.

Tentu saja Anda akan mengalokasikan:

 unsigned len = some_length_computation();
 struct my_st*p = malloc(sizeof(struct my_st)+len*sizeof(double));
 if (!p) { perror("malloc my_st"); exit(EXIT_FAILURE); };
 p->len = len;
 for (unsigned ix=0; ix<len; ix++)
    p->flexarray[ix] = log(3.0+(double)ix);

AFAIK, ini sudah mungkin di C99, dan itu sangat berguna.

BTW, anggota array fleksibel tidak ada di C ++ (karena akan sulit untuk menentukan kapan dan bagaimana mereka harus dibangun & dihancurkan). Namun lihat masa depan std :: dynarray

Basile Starynkevitch
sumber
Anda tahu, mereka hanya bisa dibatasi untuk tipe sepele, dan tidak akan ada kesulitan.
Deduplicator
2

Jika ekspresi type name[count]ditulis dalam beberapa fungsi maka Anda memberi tahu kompiler C untuk mengalokasikan pada sizeof(type)*countbyte frame tumpukan dan menghitung alamat elemen pertama dalam array.

Jika ekspresi type name[count]ditulis di luar semua definisi fungsi dan struct maka Anda memberi tahu kompiler C untuk mengalokasikan pada sizeof(type)*countbyte segmen data dan menghitung alamat elemen pertama dalam array.

namesebenarnya adalah objek konstan yang menyimpan alamat elemen pertama dalam array dan setiap objek yang menyimpan alamat dari beberapa memori disebut pointer, jadi ini adalah alasan Anda memperlakukan namesebagai pointer daripada array. Perhatikan bahwa array dalam C hanya dapat diakses melalui pointer.

Jika countadalah ekspresi konstan yang mengevaluasi ke nol maka Anda memberitahu kompiler C untuk mengalokasikan nol byte baik pada frame stack atau segmen data dan mengembalikan alamat elemen pertama dalam array, tetapi masalah dalam melakukan ini adalah bahwa elemen pertama dari array nol panjang tidak ada dan Anda tidak dapat menghitung alamat dari sesuatu yang tidak ada.

Ini rasional bahwa elemen no. count+1tidak ada dalam countarray -length, jadi ini adalah alasan bahwa kompiler C melarang untuk mendefinisikan array zero-length sebagai variabel di dalam dan di luar fungsi, karena apa isi dari nameitu? Alamat nametoko apa sebenarnya?

Jika padalah pointer maka ekspresi p[n]sama dengan*(p + n)

Di mana tanda bintang * dalam ekspresi kanan adalah operasi dereferensi penunjuk, yang berarti mengakses memori yang ditunjuk oleh p + natau mengakses memori yang alamatnya disimpan p + n, di mana p + npenunjuk ekspresi, ia mengambil alamat pdan menambahkan ke alamat ini nomor, nkalikan dengan ukuran dari jenis pointer p.

Apakah mungkin menambahkan alamat dan nomor?

Ya itu mungkin, karena alamat adalah bilangan bulat unsigned yang biasanya ditampilkan dalam notasi heksadesimal.

pengguna307542
sumber
Banyak kompiler yang digunakan untuk memungkinkan deklarasi array berukuran nol sebelum Standar melarangnya, dan banyak yang terus mengizinkan deklarasi tersebut sebagai ekstensi. Deklarasi semacam itu tidak akan menimbulkan masalah jika seseorang mengakui bahwa suatu objek ukuran Ntelah N+1mengaitkan alamat, yang pertama Nmengidentifikasi byte unik dan yang terakhir Ndimana setiap titik hanya melewati salah satu byte tersebut. Definisi seperti itu akan bekerja dengan baik bahkan dalam kasus degenerasi di mana N0.
supercat
1

Jika Anda ingin pointer ke alamat memori, nyatakan satu. Array sebenarnya menunjuk pada sejumlah memori yang telah Anda pesan. Array peluruhan ke pointer ketika diteruskan ke fungsi, tetapi jika memori yang mereka tuju ada di heap, tidak ada masalah. Tidak ada alasan untuk mendeklarasikan ukuran array nol.

ncmathsadist
sumber
2
Secara umum Anda tidak akan melakukan ini secara langsung tetapi sebagai hasil dari makro atau ketika mendeklarasikan array panjang variabel dengan data dinamis.
Kevin Cox
Array tidak menunjuk, tidak pernah. Ini bisa berisi pointer, dan dalam sebagian besar konteks Anda benar-benar menggunakan pointer ke elemen pertama, tapi itu cerita yang berbeda.
Deduplicator
1
Nama array IS pointer konstan ke memori yang terkandung dalam array.
ncmathsadist
1
Tidak, nama array meluruh ke pointer ke elemen pertama, dalam sebagian besar konteks. Perbedaannya seringkali penting.
Deduplicator
1

Dari masa C89 asli, ketika Standar C menetapkan bahwa sesuatu memiliki Perilaku Tidak Terdefinisi, yang dimaksud adalah "Lakukan apa pun yang akan membuat implementasi pada platform target tertentu yang paling cocok untuk tujuan yang dimaksudkan". Penulis Standar tidak ingin mencoba menebak perilaku apa yang paling cocok untuk tujuan tertentu. Implementasi C89 yang ada dengan ekstensi VLA mungkin memiliki perilaku yang berbeda, tetapi logis, ketika diberi ukuran nol (misalnya beberapa mungkin telah memperlakukan array sebagai ekspresi alamat yang menghasilkan NULL, sementara yang lain memperlakukannya sebagai ekspresi alamat yang mungkin sama dengan alamat variabel arbitrer lain, tetapi bisa dengan aman menambahkan nol tanpa menjebaknya). Jika ada kode yang bergantung pada perilaku yang berbeda, penulis Standar tidak akan

Daripada mencoba menebak implementasi yang mungkin dilakukan, atau menyarankan bahwa perilaku apa pun harus dianggap lebih unggul daripada yang lain, penulis Standar hanya mengizinkan pelaksana untuk menggunakan penilaian dalam menangani kasus itu sesuai dengan keinginan mereka. Implementasi yang menggunakan malloc () di belakang layar mungkin memperlakukan alamat array sebagai NULL (jika malloc ukuran-nol menghasilkan nol), yang menggunakan perhitungan stack-address mungkin menghasilkan pointer yang cocok dengan beberapa alamat variabel lain, dan beberapa implementasi lain mungkin melakukan hal-hal lain. Saya tidak berpikir mereka berharap bahwa penulis kompiler akan keluar dari jalan mereka untuk membuat kasus sudut nol berperilaku dengan cara yang tidak berguna sengaja.

supercat
sumber