Saya belum pernah menggunakan C dalam beberapa tahun terakhir. Ketika saya membaca pertanyaan ini hari ini saya menemukan beberapa sintaks C yang tidak saya kenal.
Rupanya di C99 sintaks berikut ini valid:
void foo(int n) {
int values[n]; //Declare a variable length array
}
Ini sepertinya fitur yang sangat berguna. Apakah pernah ada diskusi tentang menambahkannya ke standar C ++, dan jika demikian, mengapa itu dihilangkan?
Beberapa alasan potensial:
- Berbulu bagi vendor kompiler untuk diimplementasikan
- Tidak kompatibel dengan beberapa bagian standar lainnya
- Fungsi dapat ditiru dengan konstruksi C ++ lainnya
Standar C ++ menyatakan bahwa ukuran array harus berupa ekspresi konstan (8.3.4.1).
Ya, tentu saja saya menyadari bahwa dalam contoh mainan yang dapat digunakan std::vector<int> values(m);
, tetapi ini mengalokasikan memori dari tumpukan dan bukan tumpukan. Dan jika saya ingin array multidimensi seperti:
void foo(int x, int y, int z) {
int values[x][y][z]; // Declare a variable length array
}
yang vector
versi menjadi cukup canggung:
void foo(int x, int y, int z) {
vector< vector< vector<int> > > values( /* Really painful expression here. */);
}
Irisan, baris, dan kolom juga berpotensi tersebar di seluruh memori.
Melihat diskusi comp.std.c++
itu jelas bahwa pertanyaan ini cukup kontroversial dengan beberapa nama kelas berat di kedua sisi argumen. Jelas tidak jelas bahwa std::vector
selalu solusi yang lebih baik.
sumber
Jawaban:
Baru-baru ini ada diskusi tentang ini dimulai di usenet: Mengapa tidak ada VLA di C ++ 0x .
Saya setuju dengan orang-orang yang tampaknya setuju bahwa harus membuat potensi array besar di stack, yang biasanya hanya memiliki sedikit ruang, tidak baik. Argumennya adalah, jika Anda tahu ukurannya sebelumnya, Anda bisa menggunakan array statis. Dan jika Anda tidak tahu ukurannya sebelumnya, Anda akan menulis kode yang tidak aman.
C99 VLA dapat memberikan manfaat kecil karena dapat membuat array kecil tanpa membuang ruang atau memanggil konstruktor untuk elemen yang tidak digunakan, tetapi mereka akan memperkenalkan perubahan yang agak besar pada sistem tipe (Anda harus dapat menentukan tipe tergantung pada nilai runtime - ini belum ada dalam C ++ saat ini, kecuali untuk
new
specifier tipe-operator, tetapi mereka diperlakukan secara khusus, sehingga runtime-ness tidak luput dari ruang lingkupnew
operator).Anda dapat menggunakan
std::vector
, tetapi tidak persis sama, karena menggunakan memori dinamis, dan membuatnya menggunakan stack-dialokasikan sendiri tidak mudah (perataan adalah masalah juga). Ini juga tidak memecahkan masalah yang sama, karena vektor adalah wadah yang dapat diubah ukurannya, sedangkan VLA berukuran tetap. The C ++ Dinamis Array usulan ini dimaksudkan untuk memperkenalkan solusi berbasis perpustakaan, sebagai alternatif untuk bahasa berbasis VLA. Namun, itu tidak akan menjadi bagian dari C ++ 0x, sejauh yang saya tahu.sumber
T(*)[]
keT(*)[N]
- dalam C ++ ini tidak diperbolehkan, karena C ++ tidak tahu tentang "kompatibilitas tipe" - ini membutuhkan kecocokan yang tepat), ketikkan parameter, pengecualian, konstruktor dan penghancur dan barang. Saya tidak yakin apakah manfaat VLA akan benar-benar membayar semua pekerjaan itu. Tapi kemudian, saya belum pernah menggunakan VLA dalam kehidupan nyata, jadi saya mungkin tidak tahu kasus penggunaan yang baik untuk mereka.vector
tetapi membutuhkan pola penggunaan LIFO yang tetap dan mempertahankan satu atau lebih per-thread buffer yang dialokasikan secara statis yang umumnya berukuran sesuai dengan total alokasi terbesar yang dimiliki thread. pernah digunakan, tetapi yang bisa dipangkas secara eksplisit. "Alokasi" normal dalam kasus umum tidak memerlukan lebih dari salinan pointer, pengurangan pointer-dari-pointer, perbandingan integer, dan penambahan pointer; de-alokasi hanya membutuhkan salinan pointer. Tidak jauh lebih lambat dari VLA.(Latar belakang: Saya memiliki pengalaman menerapkan kompiler C dan C ++.)
Panjang array variabel di C99 pada dasarnya salah langkah. Untuk mendukung VLA, C99 harus membuat konsesi berikut ini untuk akal sehat:
sizeof x
tidak lagi selalu konstan waktu kompilasi; kompiler terkadang harus menghasilkan kode untuk mengevaluasisizeof
ekspresi-saat runtime.Membiarkan Vlas dua dimensi (
int A[x][y]
) diperlukan sintaks baru untuk menyatakan fungsi yang mengambil 2D Vlas sebagai parameter:void foo(int n, int A[][*])
.Tidak terlalu penting di dunia C ++, tetapi sangat penting bagi audiens target C dari pemrogram sistem tertanam, mendeklarasikan VLA berarti mengompres tumpukan stack Anda secara sewenang - wenang . Ini adalah stack-overflow dan crash yang dijamin . (Kapan saja Anda menyatakan
int A[n]
, Anda secara implisit menyatakan bahwa Anda memiliki 2GB stack untuk cadangan. Setelah semua, jika Anda tahu "n
pasti kurang dari 1000 di sini", maka Anda hanya akan menyatakanint A[1000]
. Mengganti integer 32-bitn
untuk1000
adalah pengakuan Anda tidak tahu seperti apa perilaku program Anda seharusnya.)Oke, jadi mari kita beralih ke berbicara tentang C ++ sekarang. Dalam C ++, kami memiliki perbedaan kuat yang sama antara "sistem tipe" dan "sistem nilai" yang dilakukan C89 ... tapi kami benar-benar mulai mengandalkannya dengan cara yang tidak dimiliki C. Sebagai contoh:
Jika
n
bukan konstanta waktu kompilasi (yaitu, jikaA
jenisnya diubah secara variatif), lalu apa jenisnyaS
? ApakahS
tipe juga hanya akan ditentukan saat runtime?Bagaimana dengan ini:
Kompiler harus menghasilkan kode untuk beberapa contoh
myfunc
. Seperti apa kode itu? Bagaimana kita dapat secara statis menghasilkan kode itu, jika kita tidak tahu tipeA1
pada waktu kompilasi?Lebih buruk lagi, bagaimana jika ternyata pada saat runtime itu
n1 != n2
, jadi begitu!std::is_same<decltype(A1), decltype(A2)>()
? Dalam hal ini, panggilan kemyfunc
bahkan tidak boleh dikompilasi , karena pengurangan tipe template harus gagal! Bagaimana mungkin kita meniru perilaku itu saat runtime?Pada dasarnya, C ++ bergerak ke arah mendorong semakin banyak keputusan ke dalam waktu kompilasi : pembuatan kode templat,
constexpr
evaluasi fungsi, dan sebagainya. Sementara itu, C99 sibuk mendorong keputusan waktu kompilasi tradisional (misalnyasizeof
) ke dalam runtime . Dengan pemikiran ini, apakah itu benar-benar bahkan masuk akal untuk mengeluarkan upaya berusaha untuk mengintegrasikan Vlas C99 bergaya dalam C ++?Seperti yang sudah ditunjukkan oleh setiap penjawab lain, C ++ menyediakan banyak mekanisme alokasi-tumpukan (
std::unique_ptr<int[]> A = new int[n];
ataustd::vector<int> A(n);
menjadi yang jelas) ketika Anda benar-benar ingin menyampaikan gagasan "Saya tidak tahu berapa banyak RAM yang mungkin saya butuhkan." Dan C ++ menyediakan model penanganan pengecualian yang bagus untuk menghadapi situasi yang tak terhindarkan bahwa jumlah RAM yang Anda butuhkan lebih besar dari jumlah RAM yang Anda miliki. Tapi semoga jawaban ini memberi Anda ide bagus mengapa VLA gaya C99 tidak cocok untuk C ++ - dan bahkan tidak cocok untuk C99. ;)Untuk lebih lanjut tentang topik ini, lihat N3810 "Alternatif untuk Ekstensi Array" , makalah Bjarne Stroustrup Oktober 2013 tentang VLA. Pjar Bjarne sangat berbeda dengan milikku; N3810 lebih fokus pada menemukan ish C ++ yang bagus sintaksis untuk hal-hal, dan pada mengecilkan penggunaan array mentah di C ++, sedangkan saya lebih fokus pada implikasi untuk metaprogramming dan sistem types. Saya tidak tahu apakah dia menganggap implikasi metaprogramming / sistem huruf diselesaikan, dipecahkan, atau hanya tidak menarik.
Sebuah posting blog yang bagus yang mengenai banyak poin yang sama ini adalah "Penggunaan Array Panjang Variabel yang Sah" (Chris Wellons, 2019-10-27).
sumber
alloca()
seharusnya sudah distandarisasi dalam C99. VLA adalah apa yang terjadi ketika komite standar melompat lebih dulu dari implementasi, bukan sebaliknya.*
adalah opsional, Anda dapat (dan harus) menulisint A[][n]
; (3) Anda dapat menggunakan sistem tipe tanpa benar-benar mendeklarasikan VLA apa pun. Sebagai contoh, suatu fungsi dapat menerima array dengan tipe yang dimodifikasi secara variatif, dan dapat dipanggil dengan array 2-D non-VLA dengan dimensi yang berbeda. Namun Anda membuat poin yang valid di bagian akhir posting Anda.n
dalam kasus uji Anda, dan berapa ukuran tumpukan Anda? Saya sarankan Anda mencoba memasukkan nilain
setidaknya sebesar ukuran tumpukan Anda. (Dan jika tidak ada cara bagi pengguna untuk mengontrol nilain
dalam program Anda, maka saya sarankan Anda hanya menyebarkan nilai maksimumn
langsung ke dalam deklarasi: menyatakanint A[1000]
atau apa pun yang Anda butuhkan. VLA hanya diperlukan, dan hanya berbahaya, ketika nilai maksimum darin
tidak dibatasi oleh konstanta waktu kompilasi kecil apa pun.)Anda selalu dapat menggunakan alokasi () untuk mengalokasikan memori pada stack saat runtime, jika Anda menginginkan:
Sedang dialokasikan pada tumpukan menyiratkan bahwa itu akan secara otomatis dibebaskan ketika tumpukan dibuka.
Catatan cepat: Seperti yang disebutkan dalam halaman manual Mac OS X untuk alokasi (3), "Fungsi alokasi () bergantung pada mesin dan kompiler; penggunaannya tidak diganggu." Asal kamu tahu.
sumber
if (!p) { p = alloca(strlen(foo)+1); strcpy(p, foo); }
Ini tidak dapat dilakukan dengan VLA, justru karena ruang lingkup blok mereka.C
solusi yang mirip, dan bukan yang sebenarnyaC++
.Dalam pekerjaan saya sendiri, saya menyadari bahwa setiap kali saya menginginkan sesuatu seperti array atau alokasi otomatis panjang variabel (), saya tidak terlalu peduli bahwa memori secara fisik terletak di cpu stack, hanya saja memori itu berasal dari beberapa pengalokasi tumpukan yang tidak menimbulkan perjalanan lambat ke tumpukan umum. Jadi saya punya objek per-thread yang memiliki beberapa memori yang dapat mendorong / pop buffer ukuran variabel. Pada beberapa platform saya membiarkan ini tumbuh melalui mmu. Platform lain memiliki ukuran tetap (biasanya disertai dengan tumpukan cpu ukuran tetap juga karena tidak ada mmu). Satu platform yang bekerja dengan saya (konsol permainan genggam) memiliki tumpukan cpu kecil yang berharga karena berada di memori yang langka dan cepat.
Saya tidak mengatakan bahwa mendorong buffer berukuran variabel ke dalam cpu stack tidak pernah diperlukan. Jujur saya kaget ketika saya menemukan ini bukan standar, karena sepertinya konsep itu cukup cocok dengan bahasa. Bagi saya, persyaratan "ukuran variabel" dan "harus secara fisik terletak pada cpu stack" tidak pernah muncul bersamaan. Sudah tentang kecepatan, jadi saya membuat semacam "tumpukan paralel untuk buffer data".
sumber
Ada situasi di mana mengalokasikan memori tumpukan sangat mahal dibandingkan dengan operasi yang dilakukan. Contohnya adalah matriks matematika. Jika Anda bekerja dengan matriks bertubuh kecil mengatakan 5 hingga 10 elemen dan melakukan banyak aritmatika, biaya overhead malloc akan sangat signifikan. Pada saat yang sama membuat ukuran konstanta waktu kompilasi tampaknya sangat boros dan tidak fleksibel.
Saya pikir C ++ sangat tidak aman dalam dirinya sendiri sehingga argumen untuk "mencoba untuk tidak menambahkan lebih banyak fitur yang tidak aman" tidak terlalu kuat. Di sisi lain, karena C ++ bisa dibilang adalah fitur bahasa pemrograman runtime paling efisien yang membuatnya selalu lebih berguna: Orang yang menulis program kritis kinerja sebagian besar akan menggunakan C ++, dan mereka membutuhkan kinerja sebanyak mungkin. Memindahkan barang dari tumpukan ke tumpukan adalah salah satu kemungkinannya. Mengurangi jumlah heap block adalah hal lain. Mengizinkan VLA sebagai anggota objek akan menjadi salah satu cara untuk mencapai ini. Saya sedang mengerjakan saran semacam itu. Memang agak rumit untuk diterapkan, diakui, tetapi tampaknya cukup bisa dilakukan.
sumber
Tampaknya akan tersedia di C ++ 14:
https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays
Pembaruan: Tidak berhasil masuk ke C ++ 14.
sumber
Ini dianggap untuk dimasukkan dalam C ++ / 1x, tetapi dibatalkan (ini adalah koreksi dari apa yang saya katakan sebelumnya).
Lagipula itu akan kurang berguna di C ++ karena kita sudah harus
std::vector
mengisi peran ini.sumber
std::vector
alih-alih, katakanlahalloca()
,.Gunakan std :: vector untuk ini. Sebagai contoh:
Memori akan dialokasikan pada heap, tetapi ini hanya memiliki sedikit kekurangan kinerja. Selain itu, sebaiknya tidak mengalokasikan data besar pada tumpukan, karena ukurannya agak terbatas.
sumber
std::vector<int> values(n);
? Dengan menggunakanresize
setelah konstruksi, Anda melarang jenis yang tidak dapat dipindahkan.C99 memungkinkan VLA. Dan itu menempatkan beberapa batasan tentang cara mendeklarasikan VLA. Untuk detail, lihat 6.7.5.2 dari standar. C ++ melarang VLA. Tetapi g ++ memungkinkannya.
sumber
Array seperti ini adalah bagian dari C99, tetapi bukan bagian dari standar C ++. seperti yang orang lain katakan, vektor selalu merupakan solusi yang jauh lebih baik, yang mungkin mengapa array ukuran variabel tidak dalam standar C ++ (atau dalam standar C ++ 0x yang diusulkan).
BTW, untuk pertanyaan tentang "mengapa" standar C ++ seperti itu, newsgroup Usenet yang dimoderatori comp.std.c ++ adalah tempat untuk dituju.
sumber
Jika Anda tahu nilai pada waktu kompilasi Anda dapat melakukan hal berikut:
Sunting: Anda dapat membuat vektor yang menggunakan pengalokasi tumpukan (Alokasi), karena pengalokasi adalah parameter templat.
sumber
Saya punya solusi yang benar-benar bekerja untuk saya. Saya tidak ingin mengalokasikan memori karena fragmentasi pada rutin yang perlu dijalankan berkali-kali. Jawabannya sangat berbahaya, jadi gunakan dengan risiko Anda sendiri, tetapi memanfaatkan perakitan untuk menghemat ruang di tumpukan. Contoh saya di bawah ini menggunakan array karakter (jelas variabel berukuran lain akan membutuhkan lebih banyak memori).
Bahaya di sini banyak tetapi saya akan menjelaskan beberapa: 1. Mengubah ukuran variabel setengah jalan akan membunuh posisi stack 2. Melebihi batas array akan menghancurkan variabel lain dan kode yang mungkin 3. Ini tidak bekerja dalam 64 bit build ... perlu perakitan berbeda untuk yang itu (tapi makro mungkin bisa menyelesaikan masalah itu). 4. Khusus kompiler (mungkin mengalami kesulitan bergerak di antara kompiler). Saya belum mencoba jadi saya benar-benar tidak tahu.
sumber
esp
berubah dan akan menyesuaikan aksesnya ke stack, tetapi dalam GCC misalnya Anda hanya akan mematahkannya - setidaknya jika Anda menggunakan optimasi dan-fomit-frame-pointer
khususnya.