Mengapa penggunaan alokasi () tidak dianggap praktik yang baik?

401

alloca()mengalokasikan memori pada stack daripada pada heap, seperti pada kasus malloc(). Jadi, ketika saya kembali dari rutinitas, memori itu dibebaskan. Jadi, sebenarnya ini memecahkan masalah saya membebaskan memori yang dialokasikan secara dinamis. Membebaskan memori yang dialokasikan melalui malloc()adalah sakit kepala utama dan jika entah bagaimana terlewatkan mengarah ke semua jenis masalah memori.

Mengapa penggunaan alloca()berkecil hati meskipun fitur di atas?

Vaibhav
sumber
40
Hanya catatan singkat. Meskipun fungsi ini dapat ditemukan di sebagian besar kompiler, itu tidak diperlukan oleh standar ANSI-C dan karena itu dapat membatasi portabilitas. Hal lain adalah, bahwa Anda tidak boleh! free () pointer yang Anda dapatkan dan dibebaskan secara otomatis setelah Anda keluar dari fungsi.
merkuro
9
Juga, fungsi dengan Alokasi () tidak akan diuraikan jika dinyatakan demikian.
Justicle
2
@Justicle, dapatkah Anda memberikan penjelasan? Saya sangat ingin tahu apa yang ada di balik perilaku ini
migajek
47
Lupakan semua kebisingan tentang portabilitas, tidak perlu menelepon free(yang jelas merupakan keuntungan), ketidakmampuan untuk menyelaraskannya (jelas tumpukan alokasi jauh lebih berat) dan lain-lain. Satu-satunya alasan untuk menghindari allocaadalah untuk ukuran besar. Artinya, membuang banyak memori stack bukanlah ide yang baik, ditambah Anda memiliki kemungkinan stack overflow. Jika ini masalahnya - pertimbangkan untuk menggunakan malloca/freea
valdo
5
Aspek positif lain allocaadalah bahwa tumpukan tidak dapat terfragmentasi seperti tumpukan. Ini bisa terbukti berguna untuk aplikasi gaya run-forever yang sulit real-time, atau bahkan aplikasi yang kritis terhadap keselamatan, karena WCRU kemudian dapat dianalisis secara statis tanpa menggunakan kolam memori khusus dengan serangkaian masalah mereka sendiri (tidak ada temporal locality, sumber daya sub-optimal menggunakan).
Andreas

Jawaban:

245

Jawabannya ada di sana di manhalaman (setidaknya di Linux ):

RETURN VALUE Fungsi Alokasi () mengembalikan pointer ke awal ruang yang dialokasikan. Jika alokasi menyebabkan stack overflow, perilaku program tidak ditentukan.

Yang tidak mengatakan itu tidak boleh digunakan. Salah satu proyek OSS yang saya kerjakan menggunakannya secara luas, dan selama Anda tidak menyalahgunakannya ( allocanilai-nilai yang sangat besar), tidak apa-apa. Setelah Anda melewati tanda "beberapa ratus byte", sekarang saatnya untuk menggunakan mallocdan berteman. Anda mungkin masih mendapatkan kegagalan alokasi, tetapi setidaknya Anda akan memiliki beberapa indikasi kegagalan alih-alih hanya meniup tumpukan.

Sean Bright
sumber
35
Jadi benar-benar tidak ada masalah dengan itu bahwa Anda juga tidak perlu mendeklarasikan array besar?
TED
88
@Sean: Ya, risiko overflow tumpukan adalah alasan yang diberikan, tapi alasan itu agak konyol. Pertama karena (seperti yang dikatakan Vaibhav) array lokal yang besar menyebabkan masalah yang persis sama, tetapi hampir tidak difitnah. Juga, rekursi dapat dengan mudah meniup stack. Maaf tapi saya ucapkan terima kasih kepada Anda untuk dengan mudah melawan gagasan yang ada bahwa alasan yang diberikan di halaman manual dibenarkan.
j_random_hacker
49
Maksud saya adalah bahwa pembenaran yang diberikan dalam halaman manual tidak masuk akal, karena mengalokasikan () persis sebagai "buruk" seperti hal-hal lain (array lokal atau fungsi rekursif) yang dianggap halal.
j_random_hacker
39
@ninjalj: Bukan oleh programmer C / C ++ yang sangat berpengalaman, tapi saya pikir banyak orang yang takut alloca()tidak memiliki ketakutan yang sama terhadap array atau rekursi lokal (pada kenyataannya banyak orang yang akan berteriak alloca()akan memuji rekursi karena "terlihat elegan") . Saya setuju dengan saran Shaun ("Alokasi () baik untuk alokasi kecil") tetapi saya tidak setuju dengan pola pikir yang menetapkan Alokasi () sebagai kejahatan unik di antara 3 - mereka sama-sama berbahaya!
j_random_hacker
35
Catatan: Mengingat strategi alokasi memori "optimistis" Linux, Anda kemungkinan besar tidak akan mendapatkan indikasi kegagalan heap-exhaustion ... sebaliknya malloc () akan memberi Anda pointer non-NULL yang bagus, dan kemudian ketika Anda mencoba untuk benar-benar mengakses ruang alamat yang ditunjuknya, proses Anda (atau proses lain, tak terduga) akan terbunuh oleh pembunuh-OOM. Tentu saja ini adalah "fitur" dari Linux daripada masalah C / C ++ per se, tetapi itu sesuatu yang perlu diingat ketika memperdebatkan apakah alokasi () atau malloc () lebih "aman". :)
Jeremy Friesner
209

Salah satu bug paling berkesan yang saya miliki adalah hubungannya dengan fungsi inline yang digunakan alloca. Itu memanifestasikan dirinya sebagai stack overflow (karena mengalokasikan pada stack) pada titik-titik acak dari eksekusi program.

Dalam file header:

void DoSomething() {
   wchar_t* pStr = alloca(100);
   //......
}

Dalam file implementasi:

void Process() {
   for (i = 0; i < 1000000; i++) {
     DoSomething();
   }
}

Jadi yang terjadi adalah DoSomethingfungsi iniler kompiler dan semua alokasi stack terjadi di dalam Process()fungsi dan dengan demikian meledakkan stack. Dalam pembelaan saya (dan saya bukan orang yang menemukan masalah; Saya harus pergi dan menangis ke salah satu pengembang senior ketika saya tidak bisa memperbaikinya), itu tidak lurus alloca, itu adalah salah satu dari konversi string ATL makro.

Jadi pelajarannya adalah - jangan gunakan allocadalam fungsi yang Anda pikir mungkin diuraikan.

Igor Zevaka
sumber
91
Menarik. Tetapi bukankah itu memenuhi syarat sebagai bug penyusun? Setelah semua, inlining mengubah perilaku kode (itu menunda pembebasan ruang yang dialokasikan menggunakan alokasi).
sleske
60
Tampaknya, setidaknya GCC akan mempertimbangkan hal ini: "Perhatikan bahwa penggunaan tertentu dalam definisi fungsi dapat membuatnya tidak cocok untuk substitusi inline. Di antara penggunaan ini adalah: penggunaan varargs, penggunaan alokasi, [...]". gcc.gnu.org/onlinedocs/gcc/Inline.html
sleske
139
Kompiler apa yang Anda merokok?
Thomas Eding
22
Apa yang saya tidak mengerti adalah mengapa kompiler tidak memanfaatkan ruang lingkup untuk menentukan bahwa alokasi di subscope lebih atau kurang "dibebaskan": penunjuk tumpukan dapat kembali ke titik sebelum memasuki ruang lingkup, seperti apa yang dilakukan ketika kembali dari fungsi (bukan?)
moala
8
Saya telah downvoted, tetapi jawabannya ditulis dengan baik: Saya setuju dengan orang lain Anda salah mengalokasikan untuk apa yang jelas merupakan bug kompiler . Compiler telah membuat asumsi yang salah dalam optimasi yang seharusnya tidak dilakukan. Mengatasi bug kompiler baik-baik saja, tapi saya tidak akan menyalahkan apa pun kecuali kompiler.
Evan Carroll
75

Pertanyaan lama tetapi tidak ada yang menyebutkan bahwa itu harus diganti dengan array panjang variabel.

char arr[size];

dari pada

char *arr=alloca(size);

Itu di C99 standar dan ada sebagai ekstensi kompiler di banyak kompiler.

Patrick Schlüter
sumber
5
Ini disebutkan oleh Jonathan Leffler pada komentar atas jawaban Arthur Ulfeldt.
ninjalj
2
Memang, tapi itu menunjukkan juga betapa mudahnya ketinggalan, karena saya belum melihatnya meskipun membaca semua tanggapan sebelum memposting.
Patrick Schlüter
6
Satu catatan - itu adalah array panjang variabel, bukan array dinamis. Yang terakhir resizable dan biasanya diimplementasikan di heap.
Tim Čas
1
Visual Studio 2015 mengkompilasi beberapa C ++ memiliki masalah yang sama.
ahcox
2
Linus Torvalds tidak suka VLA di kernel Linux . Pada versi 4.20 Linux seharusnya hampir bebas VLA.
Cristian Ciupitu
60

Alokasi () sangat berguna jika Anda tidak dapat menggunakan variabel lokal standar karena ukurannya perlu ditentukan saat runtime dan Anda benar - benar dapat menjamin bahwa pointer yang Anda dapatkan dari Alokasi () TIDAK akan pernah digunakan setelah fungsi ini kembali .

Anda bisa cukup aman jika Anda

  • jangan kembalikan pointer, atau apapun yang mengandung itu.
  • jangan menyimpan pointer dalam struktur apa pun yang dialokasikan pada heap
  • jangan biarkan utas lainnya menggunakan pointer

Bahaya sebenarnya datang dari kemungkinan bahwa orang lain akan melanggar kondisi ini beberapa waktu kemudian. Dengan mengingat hal itu, sangat bagus untuk meneruskan buffer ke fungsi yang memformat teks ke dalamnya :)

Arthur Ulfeldt
sumber
12
Fitur VLA (variable length array) dari C99 mendukung variabel lokal berukuran dinamis tanpa secara eksplisit membutuhkan pengalokasian () untuk digunakan.
Jonathan Leffler
2
neato! menemukan info lebih lanjut di bagian '3,4 Variable Length Array' dari programmersheaven.com/2/Pointers-and-Arrays-page-2
Arthur Ulfeldt
1
Tetapi itu tidak berbeda dari penanganan dengan pointer ke variabel lokal. Mereka juga bisa dibodohi ...
glglgl
2
@Jonathan Leffler satu hal yang dapat Anda lakukan dengan alokasi tetapi Anda tidak dapat melakukannya dengan VLA menggunakan pembatasan kata kunci dengan mereka. Seperti ini: float * membatasi heavy_used_arr = mengalokasikan (sizeof (float) * size); alih-alih float heavy_used_arr [size]. Mungkin membantu beberapa kompiler (gcc 4.8 dalam kasus saya) untuk mengoptimalkan perakitan bahkan jika ukurannya adalah konstanta kompilasi. Lihat pertanyaan saya tentang hal ini: stackoverflow.com/questions/19026643/using-restrict-with-arrays
Piotr Lopusiewicz
@JonathanLeffler A VLA adalah lokal ke blok yang berisi itu. Di sisi lain, alloca()alokasikan memori yang bertahan hingga akhir fungsi. Ini berarti bahwa tampaknya tidak ada terjemahan langsung dan mudah ke VLA dari f() { char *p; if (c) { /* compute x */ p = alloca(x); } else { p = 0; } /* use p */ }. Jika Anda pikir mungkin untuk secara otomatis menerjemahkan penggunaan allocake penggunaan VLA tetapi membutuhkan lebih dari komentar untuk menjelaskan caranya, saya dapat membuat ini menjadi pertanyaan.
Pascal Cuoq
40

Seperti disebutkan dalam posting newsgroup ini , ada beberapa alasan mengapa menggunakan allocabisa dianggap sulit dan berbahaya:

  • Tidak semua dukungan kompiler alloca.
  • Beberapa kompiler menginterpretasikan perilaku yang diinginkan secara allocaberbeda, sehingga portabilitas tidak dijamin bahkan di antara kompiler yang mendukungnya.
  • Beberapa implementasi bersifat buggy.
GratisMemory
sumber
24
Satu hal yang saya lihat disebutkan pada tautan itu yang tidak ada di tempat lain di halaman ini adalah bahwa fungsi yang menggunakan alloca()memerlukan register terpisah untuk memegang penunjuk tumpukan dan penunjuk bingkai. Pada CPU x86> = 386, penunjuk tumpukan ESPdapat digunakan untuk keduanya, membebaskan EBP- kecuali jika alloca()digunakan.
j_random_hacker
10
Poin bagus lainnya pada halaman itu adalah bahwa kecuali generator kode kompilator menanganinya sebagai kasus khusus, f(42, alloca(10), 43);bisa macet karena kemungkinan bahwa penunjuk tumpukan disesuaikan alloca() setelah setidaknya salah satu argumen didorong di atasnya.
j_random_hacker
3
Posting yang ditautkan tampaknya ditulis oleh John Levine - dude yang menulis "Linkers and Loaders", saya akan percaya apa pun yang dia katakan.
user318904
3
Posting yang terhubung adalah balasan untuk posting oleh John Levine.
A. Wilcox
6
Ingatlah, banyak yang telah berubah sejak tahun 1991. Semua kompiler C modern (bahkan pada tahun 2009) harus menangani alokasi sebagai kasus khusus; ini merupakan fungsi intrinsik daripada fungsi biasa, dan bahkan mungkin tidak memanggil fungsi. Jadi, masalah alokasi-dalam-parameter (yang muncul dalam K&R C dari tahun 1970-an) seharusnya tidak menjadi masalah sekarang. Lebih detail dalam komentar yang saya buat pada jawaban Tony D
greggo
26

Satu masalah adalah itu bukan standar, meskipun didukung secara luas. Hal lain dianggap sama, saya selalu menggunakan fungsi standar daripada ekstensi kompiler umum.

David Thornley
sumber
21

masih mengalokasikan penggunaan tidak dianjurkan, mengapa?

Saya tidak melihat konsensus seperti itu. Banyak pro kuat; beberapa kontra:

  • C99 menyediakan array panjang variabel, yang sering digunakan secara istimewa sebagai notasi lebih konsisten dengan array panjang tetap dan keseluruhan intuitif
  • banyak sistem memiliki lebih sedikit memori / alamat-ruang yang tersedia untuk stack daripada yang mereka lakukan untuk heap, yang membuat program sedikit lebih rentan terhadap kelelahan memori (melalui stack overflow): ini dapat dilihat sebagai hal yang baik atau buruk - satu alasan tumpukan tidak secara otomatis menumbuhkan cara tumpukan adalah untuk mencegah program yang tidak terkendali memiliki dampak negatif yang sama besar pada seluruh mesin
  • ketika digunakan dalam lingkup yang lebih lokal (seperti a whileatau forloop) atau dalam beberapa lingkup, memori menumpuk per iterasi / ruang lingkup dan tidak dirilis sampai fungsi keluar: ini kontras dengan variabel normal yang didefinisikan dalam lingkup struktur kontrol (misalnya for {int i = 0; i < 2; ++i) { X }akan menumpukalloca memori yang diminta pada X, tetapi memori untuk array berukuran tetap akan didaur ulang per iterasi).
  • kompiler modern biasanya tidak inlineberfungsi yang memanggil alloca, tetapi jika Anda memaksa mereka makaalloca akan terjadi dalam konteks pemanggil (yaitu tumpukan tidak akan dirilis sampai pemanggil kembali)
  • dahulu kala alloca beralih dari fitur non-portable / hack ke ekstensi standar, tetapi beberapa persepsi negatif mungkin bertahan
  • seumur hidup terikat pada lingkup fungsi, yang mungkin cocok atau tidak sesuai dengan programmer lebih baik daripada mallockontrol eksplisit
  • harus menggunakan mallocpemikiran mendorong tentang deallokasi - jika itu dikelola melalui fungsi wrapper (misalnya WonderfulObject_DestructorFree(ptr)), maka fungsi tersebut menyediakan titik untuk implementasi operasi pembersihan (seperti menutup deskriptor file, membebaskan pointer internal atau melakukan logging) tanpa perubahan eksplisit ke klien kode: kadang model yang bagus untuk diadopsi secara konsisten
    • dalam gaya pemrograman pseudo-OO ini, itu wajar untuk menginginkan sesuatu seperti WonderfulObject* p = WonderfulObject_AllocConstructor();- itu mungkin ketika "constructor" adalah fungsi mallocmemori yang dikembalikan (karena memori tetap dialokasikan setelah fungsi mengembalikan nilai untuk disimpan dalam p), tetapi tidak jika "konstruktor" menggunakanalloca
      • versi makro dari WonderfulObject_AllocConstructorbisa mencapai ini, tetapi "makro jahat" di mana mereka dapat saling bertentangan dan kode non-makro dan membuat substitusi yang tidak diinginkan dan akibatnya sulit didiagnosis masalah
    • freeoperasi yang hilang dapat dideteksi oleh ValGrind, Purify dll. tetapi panggilan "destruktor" yang hilang tidak dapat dideteksi sama sekali - satu manfaat yang sangat lemah dalam hal penegakan penggunaan yang dimaksudkan; beberapa alloca()implementasi (seperti GCC) menggunakan makro inline alloca(), jadi substitusi runtime dari perpustakaan diagnostik penggunaan memori tidak mungkin seperti itu untuk malloc/ realloc/ free(misalnya pagar listrik)
  • beberapa implementasi memiliki masalah halus: misalnya, dari manual Linux:

    Pada banyak sistem, alokasi () tidak dapat digunakan di dalam daftar argumen pemanggilan fungsi, karena ruang tumpukan dicadangkan oleh alokasi () akan muncul di tumpukan di tengah ruang untuk argumen fungsi.


Saya tahu pertanyaan ini ditandai C, tetapi sebagai seorang programmer C ++ saya pikir saya akan menggunakan C ++ untuk menggambarkan utilitas potensial dari alloca: kode di bawah ini (dan di sini di ideone ) menciptakan vektor pelacakan jenis polimorfik berukuran berbeda yang dialokasikan stack (dengan seumur hidup terikat dengan fungsi return) daripada tumpukan dialokasikan.

#include <alloca.h>
#include <iostream>
#include <vector>

struct Base
{
    virtual ~Base() { }
    virtual int to_int() const = 0;
};

struct Integer : Base
{
    Integer(int n) : n_(n) { }
    int to_int() const { return n_; }
    int n_;
};

struct Double : Base
{
    Double(double n) : n_(n) { }
    int to_int() const { return -n_; }
    double n_;
};

inline Base* factory(double d) __attribute__((always_inline));

inline Base* factory(double d)
{
    if ((double)(int)d != d)
        return new (alloca(sizeof(Double))) Double(d);
    else
        return new (alloca(sizeof(Integer))) Integer(d);
}

int main()
{
    std::vector<Base*> numbers;
    numbers.push_back(factory(29.3));
    numbers.push_back(factory(29));
    numbers.push_back(factory(7.1));
    numbers.push_back(factory(2));
    numbers.push_back(factory(231.0));
    for (std::vector<Base*>::const_iterator i = numbers.begin();
         i != numbers.end(); ++i)
    {
        std::cout << *i << ' ' << (*i)->to_int() << '\n';
        (*i)->~Base();   // optionally / else Undefined Behaviour iff the
                         // program depends on side effects of destructor
    }
}
Tony Delroy
sumber
tidak +1 karena cara aneh penanganan amis beberapa jenis :-(
einpoklum
@einpoklum: yah itu sangat mencerahkan ... terima kasih.
Tony Delroy
1
Biarkan saya ulangi: Ini adalah jawaban yang sangat bagus. Sampai pada titik di mana saya pikir Anda menyarankan agar orang menggunakan semacam pola tandingan.
einpoklum
Komentar dari manual linux sudah sangat tua dan, saya cukup yakin, usang. Semua kompiler modern tahu apa itu alokasi (), dan tidak akan tersandung tali sepatu mereka seperti itu. Dalam K&R C lama, (1) semua fungsi menggunakan frame pointer (2) Semua panggilan fungsi adalah {push args on stack} {panggil func} {add # n, sp}. Alokasi adalah fungsi lib yang hanya akan menumpuk stack, kompiler bahkan tidak tahu tentang hal itu terjadi. (1) dan (2) tidak benar lagi sehingga alokasi tidak dapat bekerja seperti itu (sekarang ini intrinsik). Dalam C lama, memanggil alokasi di tengah mendorong argumen jelas akan mematahkan asumsi itu juga.
greggo
4
Mengenai contoh, saya akan secara umum khawatir tentang sesuatu yang membutuhkan always_inline untuk menghindari kerusakan memori ....
greggo
14

Semua jawaban lainnya benar. Namun, jika hal yang ingin Anda alokasikan menggunakan alloca()cukup kecil, saya pikir itu adalah teknik bagus yang lebih cepat dan lebih nyaman daripada menggunakan malloc()atau sebaliknya.

Dengan kata lain, alloca( 0x00ffffff )itu berbahaya dan cenderung menyebabkan meluap, persis sebanyak apa char hugeArray[ 0x00ffffff ];adanya. Berhati-hatilah dan masuk akal dan Anda akan baik-baik saja.

JSB ձոգչ
sumber
12

Banyak jawaban menarik untuk pertanyaan "lama" ini, bahkan beberapa jawaban yang relatif baru, tetapi saya tidak menemukan yang menyebutkan ini ....

Ketika digunakan dengan benar dan dengan hati-hati, penggunaan yang konsisten alloca() (mungkin di seluruh aplikasi) untuk menangani alokasi panjang variabel yang kecil (atau C99 VLA, jika tersedia) dapat menyebabkan pertumbuhan tumpukan keseluruhan yang lebih rendah daripada implementasi yang setara dengan menggunakan array lokal besar dengan panjang tetap . Jadi alloca()mungkin baik untuk tumpukan Anda jika Anda menggunakannya dengan hati-hati.

Saya menemukan kutipan itu di .... OK, saya membuat kutipan itu. Tapi sungguh, pikirkan tentang itu ....

@j_random_hacker sangat benar dalam komentarnya di bawah jawaban lain: Menghindari penggunaan yang alloca()mendukung array lokal berukuran besar tidak membuat program Anda lebih aman dari stack overflow (kecuali jika kompiler Anda sudah cukup tua untuk memungkinkan inlining fungsi yang digunakan alloca()dalam hal ini Anda harus tingkatkan, atau kecuali jika Anda menggunakan alloca()loop dalam, dalam hal ini Anda harus ... tidak menggunakan alloca()loop dalam).

Saya telah bekerja di lingkungan desktop / server dan sistem embedded. Banyak sistem tertanam tidak menggunakan tumpukan sama sekali (mereka bahkan tidak menautkan dukungan untuk itu), untuk alasan yang mencakup persepsi bahwa memori yang dialokasikan secara dinamis adalah jahat karena risiko kebocoran memori pada aplikasi yang tidak pernah pernah reboot selama bertahun-tahun pada suatu waktu, atau pembenaran yang lebih masuk akal bahwa memori dinamis berbahaya karena tidak dapat diketahui secara pasti bahwa suatu aplikasi tidak akan pernah memecah tumpukannya ke titik kehabisan memori palsu. Jadi programmer yang tertanam dibiarkan dengan beberapa alternatif.

alloca() (atau VLA) mungkin alat yang tepat untuk pekerjaan itu.

Saya telah melihat waktu & waktu lagi di mana seorang programmer membuat buffer yang dialokasikan stack "cukup besar untuk menangani setiap kasus yang mungkin". Dalam pohon panggilan yang sangat bersarang, penggunaan berulang pola (anti -?) Mengarah ke penggunaan tumpukan berlebihan. (Bayangkan pohon panggilan sedalam 20 level, di mana pada setiap level karena alasan yang berbeda, fungsinya secara membabi buta mengalokasikan buffer 1024 byte "hanya untuk aman" ketika umumnya hanya akan menggunakan 16 atau kurang dari itu, dan hanya di sangat kasus yang jarang dapat menggunakan lebih banyak.) Alternatif adalah menggunakanalloca()atau VLA dan hanya mengalokasikan ruang stack sebanyak yang dibutuhkan fungsi Anda, untuk menghindari membebani stack secara tidak perlu. Mudah-mudahan ketika satu fungsi di pohon panggilan membutuhkan alokasi yang lebih besar dari normal, yang lain di pohon panggilan masih menggunakan alokasi kecil normal mereka, dan keseluruhan penggunaan tumpukan aplikasi secara signifikan lebih kecil daripada jika setiap fungsi secara membabi buta mengalokasikan buffer lokal .

Tetapi jika Anda memilih untuk menggunakan alloca()...

Berdasarkan jawaban lain pada halaman ini, tampaknya VLA harus aman (mereka tidak menambah alokasi tumpukan jika dipanggil dari dalam satu loop), tetapi jika Anda menggunakan alloca(), berhati-hatilah untuk tidak menggunakannya di dalam loop, dan buatlah Pastikan fungsi Anda tidak dapat digarisbawahi jika ada kemungkinan ia dipanggil dalam loop fungsi lain.

phonetagger
sumber
Saya setuju dengan hal ini. Berbahaya alloca()benar, tetapi hal yang sama dapat dikatakan kebocoran memori malloc()(mengapa tidak menggunakan GC? Orang mungkin berpendapat). alloca()bila digunakan dengan hati-hati bisa sangat berguna untuk mengurangi ukuran tumpukan.
Felipe Tonello
Alasan bagus lainnya untuk tidak menggunakan memori dinamis, terutama pada embedded: itu lebih rumit daripada menempel pada stack. Menggunakan memori dinamis memerlukan prosedur khusus dan struktur data, sedangkan pada stack itu (untuk menyederhanakan hal-hal) masalah menambah / mengurangi angka yang lebih tinggi dari stackpointer.
tehftw
Sidenote: Contoh "menggunakan penyangga tetap [MAX_SIZE]" "menyoroti mengapa kebijakan memori terlalu banyak bekerja dengan baik. Program mengalokasikan memori yang mungkin tidak pernah mereka sentuh kecuali pada batas panjang buffer mereka. Jadi tidak masalah jika Linux (dan OS lain) tidak benar-benar menetapkan halaman memori sampai pertama kali digunakan (sebagai lawan dari malloc'd). Jika buffer lebih besar dari satu halaman, program hanya dapat menggunakan halaman pertama, dan tidak membuang sisa memori fisik.
Katastic Voyage
@KatasticVoyage Kecuali MAX_SIZE lebih besar dari (atau setidaknya sama dengan) ukuran ukuran halaman memori virtual sistem Anda, argumen Anda tidak tahan air. Juga pada sistem embedded tanpa memori virtual (banyak MCU tertanam tidak memiliki MMU), kebijakan memori overcommit mungkin baik dari sudut pandang "pastikan program Anda akan berjalan dalam semua situasi", tetapi jaminan datang dengan harga yang ukuran tumpukan Anda juga harus dialokasikan untuk mendukung kebijakan memori yang terlalu berkomitmen. Pada beberapa sistem tertanam, itu adalah harga yang tidak bersedia dibayar oleh beberapa produsen berbiaya rendah.
phonetagger
11

Semua orang sudah menunjukkan hal besar yang berpotensi perilaku tidak terdefinisi dari stack overflow, tetapi saya harus menyebutkan bahwa lingkungan Windows memiliki mekanisme yang bagus untuk menangkap ini menggunakan pengecualian terstruktur (SEH) dan halaman penjaga. Karena tumpukan hanya tumbuh sesuai kebutuhan, halaman penjaga ini berada di area yang tidak terisi. Jika Anda mengalokasikan ke dalamnya (dengan meluap tumpukan) pengecualian dilemparkan.

Anda dapat menangkap pengecualian SEH ini dan memanggil _resetstkoflw untuk mengatur ulang tumpukan dan melanjutkan dengan cara merry. Itu tidak ideal tapi ini mekanisme lain untuk setidaknya tahu ada yang tidak beres ketika barang itu mengenai kipas. * nix mungkin memiliki sesuatu yang mirip yang tidak saya sadari.

Saya sarankan membatasi ukuran alokasi maksimum Anda dengan membungkus alokasi dan melacaknya secara internal. Jika Anda benar-benar hardcore tentang hal itu, Anda bisa melempar beberapa ruang lingkup penjaga di bagian atas fungsi Anda untuk melacak alokasi alokasi dalam lingkup fungsi dan kewarasan memeriksa ini terhadap jumlah maksimum yang diizinkan untuk proyek Anda.

Selain itu, selain tidak memungkinkan alokasi memori tidak menyebabkan fragmentasi memori yang cukup penting. Saya tidak berpikir alokasi adalah praktik yang buruk jika Anda menggunakannya secara cerdas, yang pada dasarnya berlaku untuk semuanya. :-)

SilentDirge
sumber
Masalahnya adalah, yang alloca()dapat menuntut banyak ruang, sehingga stackpointer mendarat di heap. Dengan itu, seorang penyerang yang dapat mengontrol ukuran alloca()dan data yang masuk ke buffer itu dapat menimpa tumpukan (yang sangat buruk).
12431234123412341234123
SEH adalah hal yang hanya Windows. Itu bagus jika Anda hanya peduli tentang kode Anda yang berjalan di Windows, tetapi jika kode Anda harus lintas platform (atau jika Anda menulis kode yang hanya berjalan pada platform non-Windows), maka Anda tidak dapat mengandalkan memiliki SEH.
George
10

Alokasi () bagus dan efisien ... tetapi juga sangat rusak.

  • broken scope behavior (fungsi lingkup alih-alih ruang lingkup blok)
  • gunakan tidak konsisten dengan malloc ( mengalokasikan -tunjuk pointer tidak boleh dibebaskan, untuk selanjutnya Anda harus melacak dari mana pointer Anda berasal dari gratis () hanya yang Anda dapatkan dengan malloc () )
  • perilaku buruk ketika Anda juga menggunakan inlining (ruang lingkup kadang-kadang pergi ke fungsi penelepon tergantung apakah callee inline atau tidak).
  • tidak ada pemeriksaan batas stack
  • perilaku yang tidak terdefinisi jika terjadi kegagalan (tidak mengembalikan NULL seperti malloc ... dan apa arti kegagalan karena tidak memeriksa batas tumpukan pula ...)
  • bukan standar ansi

Dalam kebanyakan kasus, Anda dapat menggantinya menggunakan variabel lokal dan ukuran majorant. Jika digunakan untuk objek besar, meletakkannya di tumpukan biasanya merupakan ide yang lebih aman.

Jika Anda benar-benar membutuhkannya C Anda dapat menggunakan VLA (tidak ada vla di C ++, terlalu buruk). Mereka jauh lebih baik daripada alokasi () mengenai perilaku dan konsistensi ruang lingkup. Seperti yang saya lihat, VLA adalah semacam alokasi () yang dibuat benar.

Tentu saja struktur lokal atau array menggunakan majorant dari ruang yang dibutuhkan masih lebih baik, dan jika Anda tidak memiliki alokasi heap majorant menggunakan plain malloc () mungkin waras. Saya tidak melihat kasus penggunaan yang waras di mana Anda benar-benar membutuhkan alokasi () atau VLA.

Kriss
sumber
Saya tidak melihat alasan untuk downvote (tanpa komentar, omong-omong)
gd1
Hanya nama yang memiliki cakupan. allocatidak membuat nama, hanya rentang memori, yang memiliki masa pakai .
curiousguy
@curiousguy: Anda hanya bermain dengan kata-kata. Untuk variabel otomatis, saya juga dapat berbicara tentang masa pakai memori yang mendasarinya karena cocok dengan cakupan nama. Bagaimanapun masalahnya bukan bagaimana kita menyebutnya, tetapi ketidakstabilan masa hidup / ruang lingkup memori yang dikembalikan oleh alokasi dan perilaku yang luar biasa.
Kriss
2
Saya berharap Alokasi memiliki "freea" yang sesuai, dengan spesifikasi yang memanggil "Freea" akan membatalkan efek "Alokasi" yang menciptakan objek dan semua yang berikutnya, dan persyaratan bahwa penyimpanan yang 'dialokasikan' dalam suatu fucntion harus 'bebas' di dalamnya juga. Itu akan memungkinkan hampir semua implementasi untuk mendukung pengalokasian / pembekuan dengan cara yang kompatibel, akan meredakan isu-isu yang sebaris, dan umumnya membuat banyak hal lebih bersih.
supercat
2
@supercat - Saya juga berharap begitu. Untuk alasan itu (dan banyak lagi), saya menggunakan lapisan abstraksi (kebanyakan fungsi makro dan inline) sehingga saya tidak pernah menelepon allocaatau mallocatau freelangsung. Saya mengatakan hal-hal seperti {stack|heap}_alloc_{bytes,items,struct,varstruct}dan {stack|heap}_dealloc. Jadi, heap_deallocpanggil saja freedan stack_deallocjangan-pilih. Dengan cara ini, alokasi tumpukan dapat dengan mudah diturunkan ke tumpukan alokasi, dan niat lebih jelas juga.
Todd Lehman
9

Inilah alasannya:

char x;
char *y=malloc(1);
char *z=alloca(&x-y);
*z = 1;

Bukan berarti siapa pun akan menulis kode ini, tetapi argumen ukuran yang Anda sampaikan allocahampir pasti berasal dari semacam input, yang bisa bertujuan jahat untuk membuat program Anda menjadi allocasesuatu yang besar seperti itu. Lagi pula, jika ukurannya tidak berdasarkan input atau tidak memiliki kemungkinan besar, mengapa Anda tidak mendeklarasikan buffer lokal berukuran kecil dan berukuran tetap?

Hampir semua kode menggunakan alloca dan / atau C99 vlas memiliki bug serius yang akan menyebabkan crash (jika Anda beruntung) atau kompromi privilege (jika Anda tidak seberuntung itu).

R .. GitHub BERHENTI MEMBANTU ICE
sumber
1
Dunia mungkin tidak pernah tahu. :( Itu mengatakan, saya berharap Anda bisa mengklarifikasi pertanyaan yang saya miliki alloca. Anda mengatakan bahwa hampir semua kode yang menggunakannya memiliki bug, tapi saya berencana menggunakannya; Saya biasanya mengabaikan klaim seperti itu, tetapi datang dari Anda saya tidak akan. Saya sedang menulis mesin virtual dan saya ingin mengalokasikan variabel yang tidak luput dari fungsi pada stack, bukan secara dinamis, karena percepatan yang sangat besar. pendekatan yang memiliki karakteristik kinerja yang sama? Saya tahu saya bisa dekat dengan kolam memori, tapi itu masih tidak murah. Apa yang akan Anda lakukan?
GManNickG
7
Tahu apa juga berbahaya? Ini: *0 = 9;LUAR BIASA !!! Saya kira saya seharusnya tidak pernah menggunakan pointer (atau setidaknya dereferensi mereka). Err, tunggu. Saya dapat menguji untuk melihat apakah itu nol. Hmm. Saya kira saya juga dapat menguji ukuran memori yang ingin saya alokasikan melalui alloca. Pria aneh Aneh.
Thomas Eding
7
*0=9;tidak valid C. Sedangkan untuk menguji ukuran yang Anda lolos alloca, mengujinya terhadap apa? Tidak ada cara untuk mengetahui batasnya, dan jika Anda hanya akan mengujinya terhadap ukuran kecil yang diketahui aman (misalnya 8k) Anda mungkin juga hanya menggunakan array ukuran tetap pada tumpukan.
R .. GitHub BERHENTI MEMBANTU ICE
7
Masalah dengan argumen Anda "apakah ukurannya diketahui cukup kecil atau bergantung pada input dan karenanya bisa sangat besar" seperti yang saya lihat adalah bahwa itu berlaku sama kuatnya dengan rekursi. Kompromi praktis (untuk kedua kasus) adalah mengasumsikan bahwa jika ukurannya dibatasi small_constant * log(user_input)maka kita mungkin memiliki cukup memori.
j_random_hacker
1
Memang, Anda telah mengidentifikasi SATU kasus di mana VLA / alokasi berguna: algoritma rekursif di mana ruang maks yang dibutuhkan pada bingkai panggilan apa pun bisa sebesar N, tetapi di mana jumlah ruang yang dibutuhkan pada semua level rekursi adalah N atau fungsi N yang tidak tumbuh dengan cepat.
R .. GitHub BERHENTI MEMBANTU ICE
9

Saya tidak berpikir ada yang menyebutkan ini: Penggunaan alokasi dalam suatu fungsi akan menghalangi atau menonaktifkan beberapa optimasi yang dapat diterapkan dalam fungsi, karena kompiler tidak dapat mengetahui ukuran bingkai tumpukan fungsi.

Sebagai contoh, optimasi umum oleh kompiler C adalah untuk menghilangkan penggunaan frame pointer dalam suatu fungsi, akses frame dibuat relatif terhadap stack pointer sebagai gantinya; jadi ada satu register lagi untuk penggunaan umum. Tetapi jika alokasi disebut di dalam fungsi, perbedaan antara sp dan fp tidak akan diketahui untuk bagian dari fungsi, sehingga optimasi ini tidak dapat dilakukan.

Mengingat kelangkaan penggunaannya, dan statusnya yang teduh sebagai fungsi standar, perancang kompiler sangat mungkin menonaktifkan pengoptimalan apa pun yang dapat menyebabkan masalah dengan pengalokasian, jika perlu lebih dari sedikit upaya untuk membuatnya berfungsi dengan pengalokasian.

UPDATE: Karena array lokal panjang variabel telah ditambahkan ke C, dan karena ini menyajikan masalah pembuatan kode yang sangat mirip dengan kompiler sebagai alokasi, saya melihat bahwa 'kelangkaan penggunaan dan status teduh' tidak berlaku untuk mekanisme yang mendasarinya; tapi saya masih akan curiga bahwa penggunaan salah satu alokasi atau VLA cenderung kompromi pembuatan kode dalam fungsi yang menggunakannya. Saya akan menerima umpan balik dari desainer kompiler.

greggo
sumber
1
Array panjang variabel tidak pernah ditambahkan ke C ++.
Nir Friedman
@NirFriedman Memang. Saya pikir ada daftar fitur wikipedia yang didasarkan pada proposal lama.
greggo
> Saya masih akan curiga bahwa penggunaan salah satu pengalokasian atau VLA cenderung mengkompromikan pembuatan kode. Saya akan berpikir bahwa penggunaan pengalokasian memerlukan penunjuk bingkai, karena penunjuk tumpukan bergerak dengan cara yang tidak jelas pada waktu kompilasi. pengalokasian dapat dipanggil dalam satu lingkaran untuk terus meraih lebih banyak memori tumpukan, atau dengan ukuran run-time yang dihitung, dll. Jika ada bingkai penunjuk, kode yang dihasilkan memiliki referensi yang stabil ke penduduk lokal dan penunjuk tumpukan dapat melakukan apa pun yang diinginkan; itu tidak digunakan.
Kaz
8

Satu perangkap dengan allocaitu yang longjmpmemundurkannya.

Artinya, jika Anda menyimpan konteks dengan setjmp, maka allocabeberapa memori, maka longjmpke konteksnya, Anda mungkin kehilangan allocamemori. Penunjuk tumpukan kembali ke tempatnya semula sehingga memori tidak lagi dicadangkan; jika Anda memanggil suatu fungsi atau melakukan yang lain alloca, Anda akan mengalahkan yang asli alloca.

Untuk memperjelas, apa yang saya maksudkan secara khusus di sini adalah situasi di mana longjmptidak kembali dari fungsi di mana allocaterjadi! Sebaliknya, fungsi menyimpan konteks dengan setjmp; kemudian mengalokasikan memori dengan allocadan akhirnya longjmp terjadi pada konteks itu. allocaMemori fungsi itu tidak semuanya dibebaskan; hanya semua memori yang dialokasikan sejak setjmp. Tentu saja, saya berbicara tentang perilaku yang diamati; tidak ada persyaratan seperti itu yang didokumentasikanalloca yang saya tahu.

Fokus dalam dokumentasi biasanya pada konsep bahwa allocamemori dikaitkan dengan aktivasi fungsi , bukan dengan blok apa pun; bahwa banyak pemanggilan allocahanya mengambil lebih banyak memori tumpukan yang semuanya dilepaskan ketika fungsi berakhir. Tidak begitu; memori sebenarnya terkait dengan konteks prosedur. Ketika konteksnya dipulihkan longjmp, demikian juga allocakeadaan sebelumnya . Ini adalah konsekuensi dari register penunjuk tumpukan itu sendiri yang digunakan untuk alokasi, dan juga (tentu saja) disimpan dan dipulihkan di jmp_buf.

Kebetulan, ini, jika bekerja seperti itu, menyediakan mekanisme yang masuk akal untuk secara sengaja membebaskan memori yang dialokasikan bersama alloca.

Saya telah menemukan ini sebagai penyebab utama bug.

Kaz
sumber
1
Itulah yang seharusnya dilakukan - longjmpkembali dan membuatnya sehingga program lupa tentang semua yang terjadi di stack: semua variabel, pemanggilan fungsi dll. Dan allocaseperti array pada stack, jadi diharapkan mereka akan musnah seperti yang lainnya di stack.
tehftw
1
man allocamemberikan kalimat berikut: "Karena ruang yang dialokasikan oleh dialokasikan () dialokasikan dalam bingkai tumpukan, ruang itu secara otomatis dibebaskan jika fungsi kembali dilompati oleh panggilan ke longjmp (3) atau siglongjmp (3).". Jadi didokumentasikan bahwa memori yang dialokasikan dengan allocaakan musnah setelah a longjmp.
tehftw
@tehftw Situasi yang diuraikan terjadi tanpa pengembalian fungsi yang dilompati longjmp. Fungsi target belum kembali. Itu telah dilakukan setjmp, allocadan kemudian longjmp. The longjmpdapat memundurkan allocakembali negara untuk apa itu pada setjmpwaktu. Dengan kata lain, pointer yang dipindahkan oleh allocamenderita masalah yang sama dengan variabel lokal yang belum ditandai volatile!
Kaz
3
Saya tidak mengerti mengapa itu seharusnya tidak terduga. Ketika Anda setjmpkemudian alloca, dan kemudian longjmp, itu normal yang allocaakan mundur. Intinya longjmpadalah untuk kembali ke keadaan yang telah disimpan setjmp!
tehftw
@tehftw Saya belum pernah melihat interaksi khusus ini didokumentasikan. Oleh karena itu, tidak dapat diandalkan dengan cara baik, selain dengan penyelidikan empiris dengan penyusun.
Kaz
7

Sebuah tempat alloca()yang sangat berbahaya daripada malloc()kernel - kernel dari sistem operasi tipikal memiliki ruang stack berukuran tetap yang dikodekan ke dalam salah satu header-nya; itu tidak sefleksibel tumpukan aplikasi. Melakukan panggilan ke alloca()dengan ukuran yang tidak beralasan dapat menyebabkan kernel mogok. Kompiler tertentu memperingatkan penggunaan alloca()(dan bahkan VLA dalam hal ini) di bawah opsi-opsi tertentu yang harus dinyalakan saat menyusun kode kernel - di sini, lebih baik untuk mengalokasikan memori di heap yang tidak diperbaiki oleh batas hard-coded.

Sondhi Chakraborty
sumber
7
alloca()tidak lebih berbahaya daripada di int foo[bar];mana barbilangan bulat sembarang.
Todd Lehman
@ToddLehman Itu benar, dan untuk alasan yang tepat kami telah melarang VLA di kernel selama beberapa tahun, dan telah bebas VLA sejak 2018 :-)
Chris Down
6

Jika Anda secara tidak sengaja menulis di luar blok yang dialokasikan dengan alloca(karena buffer overflow misalnya), maka Anda akan menimpa alamat kembali fungsi Anda, karena yang itu terletak "di atas" pada tumpukan, yaitu setelah blok yang dialokasikan.

blok _alloca di stack

Konsekuensi dari ini adalah dua kali lipat:

  1. Program akan crash secara spektakuler dan tidak mungkin untuk mengetahui mengapa atau di mana crash (stack kemungkinan besar akan lepas ke alamat acak karena frame pointer yang ditimpa).

  2. Itu membuat buffer overflow berkali-kali lebih berbahaya, karena pengguna jahat dapat membuat muatan khusus yang akan diletakkan di tumpukan dan karenanya dapat berakhir dieksekusi.

Sebaliknya, jika Anda menulis di luar blok di heap Anda "hanya" mendapatkan korupsi tumpukan. Program ini mungkin akan berhenti secara tak terduga tetapi akan melepaskan tumpukan dengan benar, sehingga mengurangi kemungkinan eksekusi kode berbahaya.

rustyx
sumber
11
Tidak ada dalam situasi ini yang secara dramatis berbeda dari bahaya buffer-overflowing buffer yang dialokasikan stack berukuran tetap. Bahaya ini tidak unik untuk alloca.
phonetagger
2
Tentu saja tidak. Tapi tolong periksa pertanyaan aslinya. Pertanyaannya adalah: apa bahayanya allocadibandingkan dengan malloc(sehingga tidak buffer berukuran tetap pada stack).
rustyx
Titik minor, tetapi tumpukan pada beberapa sistem tumbuh ke atas (mis. Mikroprosesor PIC 16-bit).
EBlake
5

Sayangnya yang benar-benar luar biasa alloca()hilang dari tcc yang hampir luar biasa. Gcc memang punya alloca().

  1. Itu menabur benih kehancurannya sendiri. Dengan pengembalian sebagai destruktor.

  2. Seperti malloc()itu mengembalikan pointer tidak valid pada gagal yang akan segfault pada sistem modern dengan MMU (dan mudah-mudahan restart yang tanpa).

  3. Tidak seperti variabel otomatis, Anda dapat menentukan ukuran saat dijalankan.

Ini bekerja dengan baik dengan rekursi. Anda dapat menggunakan variabel statis untuk mencapai sesuatu yang mirip dengan rekursi ekor dan menggunakan hanya beberapa yang lain meneruskan info ke setiap iterasi.

Jika Anda mendorong terlalu dalam, Anda dijamin segfault (jika Anda memiliki MMU).

Catatan yang malloc()menawarkan tidak lebih karena mengembalikan NULL (yang juga akan segfault jika ditugaskan) ketika sistem kehabisan memori. Yaitu yang dapat Anda lakukan adalah jaminan atau hanya mencoba menetapkannya dengan cara apa pun.

Untuk menggunakan malloc()saya menggunakan global dan menetapkannya NULL. Jika pointer bukan NULL saya membebaskannya sebelum saya gunakan malloc().

Anda juga dapat menggunakan realloc()sebagai kasus umum jika ingin menyalin data yang ada. Anda perlu memeriksa pointer sebelum bekerja jika Anda akan menyalin atau menyatukan setelahrealloc() .

3.2.5.2 Keuntungan dari alokasi

zagam
sumber
4
Sebenarnya spec alokasi tidak mengatakan itu mengembalikan pointer tidak valid pada gagal (stack overflow) itu mengatakan ia memiliki perilaku yang tidak terdefinisi ... dan untuk malloc ia mengatakan mengembalikan NULL, bukan pointer tidak valid acak (OK, implementasi memori optimis Linux membuat tak berguna).
Kriss
@ Kriss Linux dapat membunuh proses Anda, tetapi setidaknya itu tidak berani berperilaku tidak terdefinisi
craig65535
@ craig65535: ekspresi perilaku tidak terdefinisi biasanya berarti bahwa perilaku itu tidak ditentukan oleh spesifikasi C atau C ++. Tidak dengan cara apa pun itu akan acak atau tidak stabil pada OS atau kompiler yang diberikan. Karenanya tidak ada artinya mengaitkan UB dengan nama OS seperti "Linux" atau "Windows". Itu tidak ada hubungannya dengan itu.
Kriss
Saya mencoba mengatakan bahwa malloc mengembalikan NULL, atau dalam kasus Linux, akses memori yang membunuh proses Anda, lebih disukai daripada perilaku alokasi yang tidak ditentukan. Saya pikir saya pasti salah membaca komentar pertama Anda.
craig65535
3

Proses hanya memiliki jumlah ruang stack yang terbatas - jauh lebih sedikit daripada jumlah memori yang tersedia malloc().

Dengan menggunakan alloca()Anda secara dramatis meningkatkan peluang Anda untuk mendapatkan kesalahan Stack Overflow (jika Anda beruntung, atau crash yang tidak dapat dijelaskan jika Anda tidak).

RichieHindle
sumber
Itu sangat tergantung pada aplikasi. Sudah lazim untuk aplikasi tertanam terbatas memori memiliki ukuran tumpukan lebih besar dari heap (jika ada IS heap).
EBlake
3

Tidak terlalu cantik, tetapi jika kinerja benar-benar penting, Anda dapat mengalokasikan beberapa ruang di stack.

Jika Anda sekarang sudah ukuran maksimum memori memblokir kebutuhan Anda dan Anda ingin terus memeriksa melimpah, Anda bisa melakukan sesuatu seperti:

void f()
{
    char array_on_stack[ MAX_BYTES_TO_ALLOCATE ];
    SomeType *p = (SomeType *)array;

    (...)
}
Sylvain Rodrigue
sumber
12
Apakah array char dijamin akan disejajarkan dengan benar untuk semua tipe data? Alokasi memberikan janji tersebut.
Juho Östman
@ JuhoÖstman: Anda bisa menggunakan array struct (atau tipe apa pun) alih-alih char jika Anda memiliki masalah alignment.
Kriss
Itu disebut Array Panjang Variabel . Ini didukung di C90 dan di atas, tetapi tidak C ++. Lihat Bisakah saya menggunakan Array Panjang Variabel C di C ++ 03 dan C ++ 11?
jww
3

Fungsi alokasi sangat bagus dan semua penentang hanya menyebarkan FUD.

void foo()
{
    int x = 50000; 
    char array[x];
    char *parray = (char *)alloca(x);
}

Array dan parray adalah PERSIS sama dengan risiko yang sama PERSIS. Mengatakan satu lebih baik daripada yang lain adalah pilihan sintaksis, bukan pilihan teknis.

Adapun untuk memilih variabel tumpukan vs variabel tumpukan, ada banyak keuntungan untuk program yang berjalan lama menggunakan tumpukan lebih dari tumpukan untuk variabel dengan masa hidup in-scope. Anda menghindari tumpukan tumpukan dan Anda bisa menghindari menumbuhkan ruang proses Anda dengan ruang tumpukan yang tidak digunakan (tidak dapat digunakan). Anda tidak perlu membersihkannya. Anda dapat mengontrol alokasi tumpukan pada proses.

Kenapa ini buruk?

mlwmohawk
sumber
3

Sebenarnya, alokasi tidak dijamin untuk menggunakan tumpukan. Memang, implementasi alokasi gcc-2.95 mengalokasikan memori dari heap menggunakan malloc itu sendiri. Juga implementasi yang bermasalah, dapat menyebabkan kebocoran memori dan beberapa perilaku yang tidak terduga jika Anda menyebutnya di dalam blok dengan penggunaan goto lebih lanjut. Tidak, untuk mengatakan bahwa Anda seharusnya tidak pernah menggunakannya, tetapi beberapa kali mengalokasikan mengarah ke lebih banyak overhead daripada yang dikeluarkan darinya.

pengguna7491277
sumber
Kedengarannya seolah-olah gcc-2.95 rusak alokasi dan mungkin tidak dapat digunakan dengan aman untuk program yang membutuhkan alloca. Bagaimana itu bisa membersihkan memori ketika longjmpdigunakan untuk meninggalkan frame yang melakukannya alloca? Kapan orang akan menggunakan gcc 2,95 hari ini?
Kaz
2

IMHO, alokasi dianggap praktik buruk karena semua orang takut melelahkan batas ukuran tumpukan.

Saya belajar banyak dengan membaca utas ini dan beberapa tautan lainnya:

Saya menggunakan dialokasikan terutama untuk membuat file C biasa saya dapat dikompilasi di msvc dan gcc tanpa perubahan, gaya C89, tidak ada #ifdef _MSC_VER, dll.

Terima kasih ! Utas ini membuat saya mendaftar ke situs ini :)

ytoto
sumber
Perlu diingat bahwa tidak ada yang namanya "utas" di situs ini. Stack Overflow memiliki format tanya jawab, bukan format utas diskusi. "Jawab" tidak seperti "Balas" di forum diskusi; itu berarti Anda benar-benar memberikan jawaban atas pertanyaan, dan tidak boleh digunakan untuk menanggapi jawaban atau komentar lain tentang topik tersebut. Setelah Anda memiliki setidaknya 50 rep, Anda dapat memposting komentar , tetapi pastikan untuk membaca "Kapan saya tidak boleh berkomentar?" bagian. Harap baca halaman Tentang untuk mendapatkan pemahaman yang lebih baik tentang format situs.
Adi Inbar
1

Menurut pendapat saya, alokasi (), jika tersedia, harus digunakan hanya dengan cara terbatas. Sangat mirip dengan penggunaan "goto", cukup banyak orang yang beralasan memiliki keengganan yang kuat tidak hanya untuk penggunaan, tetapi juga keberadaan, alokasi ().

Untuk penggunaan tertanam, di mana ukuran tumpukan diketahui dan batas dapat dikenakan melalui konvensi dan analisis pada ukuran alokasi, dan di mana kompiler tidak dapat ditingkatkan untuk mendukung C99 +, penggunaan alokasi () baik-baik saja, dan saya sudah diketahui menggunakannya.

Ketika tersedia, VLA mungkin memiliki beberapa keunggulan dibandingkan dengan alokasi (): Kompilator dapat menghasilkan pemeriksaan batas tumpukan yang akan menangkap akses di luar batas ketika akses gaya array digunakan (saya tidak tahu apakah ada kompiler yang melakukan ini, tetapi ia dapat dilakukan), dan analisis kode dapat menentukan apakah ekspresi akses array dibatasi dengan benar. Perhatikan bahwa, dalam beberapa lingkungan pemrograman, seperti otomotif, peralatan medis, dan avionik, analisis ini harus dilakukan bahkan untuk array ukuran tetap, baik otomatis (pada tumpukan) dan alokasi statis (global atau lokal).

Pada arsitektur yang menyimpan data dan mengembalikan alamat / frame pointer pada stack (dari apa yang saya ketahui, semuanya), setiap variabel yang dialokasikan stack dapat berbahaya karena alamat variabel dapat diambil, dan nilai input yang tidak dicentang mungkin mengizinkan segala macam kerusakan.

Portabilitas kurang menjadi perhatian di ruang tertanam, namun itu adalah argumen yang baik terhadap penggunaan alokasi () di luar keadaan yang dikendalikan dengan cermat.

Di luar ruang yang disematkan, saya telah menggunakan Alokasi () sebagian besar di dalam fungsi logging dan pemformatan untuk efisiensi, dan dalam pemindai leksikal non-rekursif, di mana struktur sementara (dialokasikan menggunakan Alokasi () dibuat selama Tokenisasi dan klasifikasi, kemudian terus-menerus objek (dialokasikan melalui malloc ()) diisi sebelum fungsi kembali.Penggunaan alokasi () untuk struktur sementara yang lebih kecil sangat mengurangi fragmentasi ketika objek persisten dialokasikan.

Daniel Glasser
sumber
1

Sebagian besar jawaban di sini sebagian besar tidak tepat: ada alasan mengapa menggunakan _alloca() berpotensi lebih buruk daripada hanya menyimpan benda besar di tumpukan.

Perbedaan utama antara penyimpanan otomatis dan _alloca()bahwa yang terakhir menderita masalah (serius) tambahan: blok yang dialokasikan tidak dikontrol oleh kompiler , jadi tidak ada cara bagi kompiler untuk mengoptimalkan atau mendaur ulangnya.

Membandingkan:

while (condition) {
    char buffer[0x100]; // Chill.
    /* ... */
}

dengan:

while (condition) {
    char* buffer = _alloca(0x100); // Bad!
    /* ... */
}

Masalah dengan yang terakhir harus jelas.

alecov
sumber
Apakah Anda memiliki contoh praktis yang menunjukkan perbedaan antara VLA dan alloca(ya, saya katakan VLA, karena allocalebih dari sekadar pencipta array ukuran statis)?
Ruslan
Ada kasus penggunaan untuk yang kedua, yang pertama tidak mendukung. Saya mungkin ingin memiliki 'n' catatan setelah loop selesai menjalankan 'n' kali - mungkin dalam daftar-tertaut atau pohon; struktur data ini kemudian dibuang ketika fungsi akhirnya kembali. Yang bukan untuk mengatakan saya akan kode apa pun seperti itu :-)
greggo
1
Dan saya akan mengatakan bahwa "kompiler tidak dapat mengendalikannya" karena itulah cara yang dialokasikan () didefinisikan; kompiler modern tahu apa itu alokasi, dan memperlakukannya secara khusus; itu bukan hanya fungsi perpustakaan seperti di tahun 80-an. C99 VLA pada dasarnya dialokasikan dengan ruang lingkup blok (dan pengetikan yang lebih baik). Tidak ada kontrol lebih atau kurang, hanya menyesuaikan diri dengan semantik yang berbeda.
greggo
@ Greggo: Jika Anda adalah downvoter, saya dengan senang hati akan mendengar mengapa Anda berpikir jawaban saya tidak berguna.
alecov
Dalam C, daur ulang bukan tugas kompiler, melainkan tugas perpustakaan c (gratis ()). Alokasi () dibebaskan saat kembali.
peterh
1

Saya tidak berpikir bahwa ada orang yang menyebutkan ini, tetapi mengalokasikan juga memiliki beberapa masalah keamanan yang serius belum tentu hadir dengan malloc (meskipun masalah ini juga muncul dengan susunan array berbasis, dinamis atau tidak). Karena memori dialokasikan pada stack, buffer overflows / underflow memiliki konsekuensi yang jauh lebih serius daripada hanya dengan malloc.

Secara khusus, alamat pengirim untuk suatu fungsi disimpan pada stack. Jika nilai ini rusak, kode Anda dapat dibuat untuk pergi ke wilayah memori yang dapat dieksekusi. Compiler berusaha keras untuk membuat ini sulit (khususnya dengan mengacak tata letak alamat). Namun, ini jelas lebih buruk dari sekadar stack overflow karena kasus terbaik adalah SEGFAULT jika nilai pengembalian rusak, tetapi juga bisa mulai mengeksekusi sepotong memori acak atau dalam kasus terburuk beberapa wilayah memori yang membahayakan keamanan program Anda .

Dyllon Gagnier
sumber