Saya selalu mendengar bahwa di C Anda harus benar-benar memperhatikan bagaimana Anda mengelola memori. Dan saya masih mulai belajar C, tapi sejauh ini, saya tidak perlu melakukan pengelolaan memori sama sekali. Saya selalu membayangkan harus melepaskan variabel dan melakukan segala macam hal yang buruk. Tapi sepertinya bukan itu masalahnya.
Dapatkah seseorang menunjukkan kepada saya (dengan contoh kode) contoh kapan Anda harus melakukan "manajemen memori"?
Jawaban:
Ada dua tempat di mana variabel bisa dimasukkan ke dalam memori. Saat Anda membuat variabel seperti ini:
int a; char c; char d[16];
Variabel dibuat di " tumpukan ". Variabel tumpukan secara otomatis dibebaskan saat berada di luar ruang lingkup (yaitu, saat kode tidak dapat menjangkau mereka lagi). Anda mungkin mendengarnya disebut variabel "otomatis", tetapi itu sudah ketinggalan zaman.
Banyak contoh pemula hanya akan menggunakan variabel tumpukan.
Tumpukan itu bagus karena otomatis, tetapi juga memiliki dua kelemahan: (1) Kompilator perlu mengetahui sebelumnya seberapa besar variabelnya, dan (b) ruang tumpukan agak terbatas. Misalnya: di Windows, di bawah pengaturan default untuk Microsoft linker, tumpukan diatur ke 1 MB, dan tidak semuanya tersedia untuk variabel Anda.
Jika Anda tidak tahu pada waktu kompilasi seberapa besar array Anda, atau jika Anda membutuhkan array atau struct yang besar, Anda memerlukan "plan B".
Rencana B disebut dengan " heap ". Anda biasanya dapat membuat variabel sebesar yang diizinkan Sistem Operasi, tetapi Anda harus melakukannya sendiri. Posting sebelumnya menunjukkan kepada Anda satu cara untuk melakukannya, meskipun ada cara lain:
int size; // ... // Set size to some value, based on information available at run-time. Then: // ... char *p = (char *)malloc(size);
(Perhatikan bahwa variabel di heap tidak dimanipulasi secara langsung, tetapi melalui pointer)
Setelah Anda membuat variabel heap, masalahnya adalah kompilator tidak dapat mengetahui saat Anda selesai menggunakannya, jadi Anda kehilangan pelepasan otomatis. Di situlah "pelepasan manual" yang Anda maksud masuk. Kode Anda sekarang bertanggung jawab untuk memutuskan kapan variabel tidak diperlukan lagi, dan melepaskannya sehingga memori dapat diambil untuk tujuan lain. Untuk kasus diatas, dengan:
free(p);
Apa yang membuat opsi kedua ini "bisnis yang buruk" adalah tidak selalu mudah untuk mengetahui kapan variabel tidak diperlukan lagi. Lupa melepaskan variabel saat Anda tidak membutuhkannya akan menyebabkan program Anda menghabiskan lebih banyak memori yang diperlukannya. Situasi ini disebut "kebocoran". Memori "bocor" tidak dapat digunakan untuk apa pun hingga program Anda berakhir dan OS memulihkan semua sumber dayanya. Masalah yang lebih buruk pun mungkin terjadi jika Anda melepaskan variabel heap secara tidak sengaja sebelumnya Anda benar-benar selesai menggunakannya.
Di C dan C ++, Anda bertanggung jawab untuk membersihkan variabel heap seperti yang ditunjukkan di atas. Namun, ada bahasa dan lingkungan seperti Java dan bahasa .NET seperti C # yang menggunakan pendekatan berbeda, di mana heap dibersihkan sendiri. Metode kedua ini, yang disebut "pengumpulan sampah", jauh lebih mudah bagi pengembang tetapi Anda harus membayar denda dalam biaya overhead dan kinerja. Itu keseimbangan.
(Saya telah menutupi banyak detail untuk memberikan jawaban yang lebih sederhana, tetapi mudah-mudahan lebih rata)
sumber
malloc()
, penyebabnya UB,(char *)malloc(size);
lihat stackoverflow.com/questions/605845/…Berikut contohnya. Misalkan Anda memiliki fungsi strdup () yang menduplikasi string:
char *strdup(char *src) { char * dest; dest = malloc(strlen(src) + 1); if (dest == NULL) abort(); strcpy(dest, src); return dest; }
Dan Anda menyebutnya seperti ini:
main() { char *s; s = strdup("hello"); printf("%s\n", s); s = strdup("world"); printf("%s\n", s); }
Anda dapat melihat bahwa program tersebut berfungsi, tetapi Anda telah mengalokasikan memori (melalui malloc) tanpa membebaskannya. Anda telah kehilangan penunjuk Anda ke blok memori pertama saat Anda memanggil strdup untuk kedua kalinya.
Ini bukan masalah besar untuk jumlah memori kecil ini, tetapi pertimbangkan kasusnya:
for (i = 0; i < 1000000000; ++i) /* billion times */ s = strdup("hello world"); /* 11 bytes */
Anda sekarang telah menggunakan 11 gig memori (mungkin lebih, tergantung pada pengelola memori Anda) dan jika Anda belum crash proses Anda mungkin berjalan cukup lambat.
Untuk memperbaikinya, Anda perlu memanggil free () untuk semua yang diperoleh dengan malloc () setelah Anda selesai menggunakannya:
s = strdup("hello"); free(s); /* now not leaking memory! */ s = strdup("world"); ...
Semoga contoh ini membantu!
sumber
Anda harus melakukan "manajemen memori" saat Anda ingin menggunakan memori di heap daripada di stack. Jika Anda tidak tahu seberapa besar membuat array hingga runtime, Anda harus menggunakan heap. Misalnya, Anda mungkin ingin menyimpan sesuatu dalam string, tetapi tidak tahu seberapa besar isinya hingga program dijalankan. Dalam hal ini Anda akan menulis sesuatu seperti ini:
char *string = malloc(stringlength); // stringlength is the number of bytes to allocate // Do something with the string... free(string); // Free the allocated memory
sumber
Saya pikir cara paling ringkas untuk menjawab pertanyaan untuk mempertimbangkan peran penunjuk di C. Penunjuk adalah mekanisme ringan namun kuat yang memberi Anda kebebasan besar dengan mengorbankan kapasitas yang sangat besar untuk menembak diri sendiri di kaki.
Di C tanggung jawab untuk memastikan petunjuk Anda menunjuk ke memori yang Anda miliki adalah milik Anda dan milik Anda sendiri. Ini membutuhkan pendekatan yang terorganisir dan disiplin, kecuali jika Anda mengabaikan petunjuk, yang membuatnya sulit untuk menulis C yang efektif.
Jawaban yang diposting hingga saat ini berkonsentrasi pada alokasi variabel tumpukan dan otomatis. Menggunakan alokasi tumpukan memang membuat memori yang dikelola secara otomatis dan nyaman, tetapi dalam beberapa keadaan (buffer besar, algoritme rekursif) dapat menyebabkan masalah tumpukan overflow yang menghebohkan. Mengetahui dengan tepat berapa banyak memori yang dapat Anda alokasikan pada stack sangat bergantung pada sistem. Dalam beberapa skenario tersemat, beberapa lusin byte mungkin menjadi batas Anda, dalam beberapa skenario desktop Anda dapat menggunakan megabyte dengan aman.
Alokasi heap kurang melekat pada bahasa. Ini pada dasarnya adalah serangkaian panggilan perpustakaan yang memberi Anda kepemilikan blok memori dengan ukuran tertentu sampai Anda siap untuk mengembalikannya ('gratis'). Kedengarannya sederhana, tetapi dikaitkan dengan kesedihan programmer yang tak terhitung. Masalahnya sederhana (membebaskan memori yang sama dua kali, atau tidak sama sekali [kebocoran memori], tidak mengalokasikan cukup memori [buffer overflow], dll) tetapi sulit untuk dihindari dan di-debug. Pendekatan yang sangat disiplin mutlak wajib dalam praktiknya, tetapi tentu saja bahasanya tidak benar-benar mengharuskannya.
Saya ingin menyebutkan jenis alokasi memori lain yang telah diabaikan oleh posting lain. Dimungkinkan untuk mengalokasikan variabel secara statis dengan mendeklarasikannya di luar fungsi apa pun. Saya pikir secara umum jenis alokasi ini mendapat reputasi buruk karena digunakan oleh variabel global. Namun tidak ada yang mengatakan satu-satunya cara untuk menggunakan memori yang dialokasikan dengan cara ini adalah sebagai variabel global yang tidak disiplin dalam kode spaghetti yang berantakan. Metode alokasi statis dapat digunakan hanya untuk menghindari beberapa perangkap metode heap dan alokasi otomatis. Beberapa pemrogram C terkejut mengetahui bahwa program tertanam dan permainan C yang besar dan canggih telah dibuat tanpa menggunakan alokasi heap sama sekali.
sumber
Ada beberapa jawaban bagus di sini tentang cara mengalokasikan dan membebaskan memori, dan menurut saya sisi yang lebih menantang dalam menggunakan C adalah memastikan bahwa satu-satunya memori yang Anda gunakan adalah memori yang telah Anda alokasikan - jika ini tidak dilakukan dengan benar, apa yang Anda akhiri up dengan adalah sepupu situs ini - buffer overflow - dan Anda mungkin menimpa memori yang sedang digunakan oleh aplikasi lain, dengan hasil yang sangat tidak terduga.
Sebuah contoh:
int main() { char* myString = (char*)malloc(5*sizeof(char)); myString = "abcd"; }
Pada titik ini Anda telah mengalokasikan 5 byte untuk myString dan mengisinya dengan "abcd \ 0" (string diakhiri dengan null - \ 0). Jika alokasi string Anda adalah
myString = "abcde";
Anda akan menetapkan "abcde" dalam 5 byte yang telah Anda alokasikan untuk program Anda, dan karakter nol di akhir akan diletakkan di akhir ini - bagian dari memori yang belum dialokasikan untuk Anda gunakan dan mungkin saja gratis, tetapi bisa juga digunakan oleh aplikasi lain - Ini adalah bagian penting dari manajemen memori, di mana kesalahan akan memiliki konsekuensi yang tidak dapat diprediksi (dan terkadang tidak dapat diulang).
sumber
strcpy()
bukan=
; Saya berasumsi bahwa itulah niat Chris BC.Satu hal yang perlu diingat adalah selalu menginisialisasi pointer Anda ke NULL, karena pointer yang tidak diinisialisasi mungkin berisi alamat memori valid pseudorandom yang dapat membuat kesalahan pointer terus berlanjut secara diam-diam. Dengan memaksakan sebuah pointer untuk diinisialisasi dengan NULL, Anda selalu dapat menangkap jika Anda menggunakan pointer ini tanpa memulainya. Alasannya adalah bahwa sistem operasi "menghubungkan" alamat virtual 0x00000000 ke pengecualian perlindungan umum untuk menjebak penggunaan pointer nol.
sumber
Anda juga mungkin ingin menggunakan alokasi memori dinamis ketika Anda perlu mendefinisikan array yang besar, katakanlah int [10000]. Anda tidak bisa begitu saja meletakkannya di tumpukan karena kemudian, hm ... Anda akan mendapatkan tumpukan melimpah.
Contoh bagus lainnya adalah implementasi struktur data, misalnya daftar tertaut atau pohon biner. Saya tidak memiliki kode contoh untuk ditempel di sini, tetapi Anda dapat menggunakan Google dengan mudah.
sumber
(Saya menulis karena saya merasa jawabannya sejauh ini belum tepat.)
Alasan Anda memiliki manajemen memori yang layak disebut adalah ketika Anda memiliki masalah / solusi yang mengharuskan Anda membuat struktur yang kompleks. (Jika program Anda macet jika Anda mengalokasikan ke banyak ruang pada tumpukan sekaligus, itu bug.) Biasanya, struktur data pertama yang perlu Anda pelajari adalah semacam daftar . Inilah satu yang terhubung, di luar kepala saya:
typedef struct listelem { struct listelem *next; void *data;} listelem; listelem * create(void * data) { listelem *p = calloc(1, sizeof(listelem)); if(p) p->data = data; return p; } listelem * delete(listelem * p) { listelem next = p->next; free(p); return next; } void deleteall(listelem * p) { while(p) p = delete(p); } void foreach(listelem * p, void (*fun)(void *data) ) { for( ; p != NULL; p = p->next) fun(p->data); } listelem * merge(listelem *p, listelem *q) { while(p != NULL && p->next != NULL) p = p->next; if(p) { p->next = q; return p; } else return q; }
Secara alami, Anda menginginkan beberapa fungsi lain, tetapi pada dasarnya, inilah yang Anda butuhkan untuk manajemen memori. Saya harus menunjukkan bahwa ada sejumlah trik yang mungkin dilakukan dengan manajemen memori "manual", misalnya,
Dapatkan debugger yang bagus ... Semoga berhasil!
sumber
@ Euro Micelli
Satu negatif untuk ditambahkan adalah bahwa pointer ke tumpukan tidak lagi valid ketika fungsi tersebut kembali, jadi Anda tidak dapat mengembalikan pointer ke variabel tumpukan dari suatu fungsi. Ini adalah kesalahan umum dan alasan utama mengapa Anda tidak dapat bertahan hanya dengan menumpuk variabel. Jika fungsi Anda perlu mengembalikan pointer, maka Anda harus malloc dan berurusan dengan manajemen memori.
sumber
Anda benar, tentu saja. Saya percaya itu selalu benar, meskipun saya tidak memiliki salinan K&R untuk diperiksa.
Saya tidak suka banyak konversi implisit di C, jadi saya cenderung menggunakan gips untuk membuat "sihir" lebih terlihat. Terkadang membantu keterbacaan, terkadang tidak, dan terkadang menyebabkan bug diam ditangkap oleh kompilator. Tetap saja, saya tidak memiliki pendapat yang kuat tentang ini, dengan satu atau lain cara.
Ya ... Anda menangkap saya di sana. Saya menghabiskan lebih banyak waktu di C ++ daripada C. Terima kasih telah memperhatikannya.
sumber
Di C, Anda sebenarnya memiliki dua pilihan berbeda. Pertama, Anda dapat membiarkan sistem mengelola memori untuk Anda. Atau, Anda bisa melakukannya sendiri. Umumnya, Anda ingin tetap berpegang pada yang pertama selama mungkin. Namun, memori yang dikelola secara otomatis di C sangat terbatas dan Anda perlu mengelola memori secara manual dalam banyak kasus, seperti:
Sebuah. Anda ingin variabel hidup lebih lama dari fungsinya, dan Anda tidak ingin memiliki variabel global. ex:
b. Anda ingin memiliki memori yang dialokasikan secara dinamis. Contoh paling umum adalah array tanpa panjang tetap:
Lihat, nilai panjang sudah cukup untuk menampung APA SAJA. Ingatlah untuk membebaskannya, atau Anda AKAN menyesal. Ini adalah salah satu trik favorit saya untuk bersenang-senang di C: D.
Namun, secara umum, Anda ingin menghindari trik favorit Anda (T___T). Anda AKAN merusak OS Anda, cepat atau lambat, jika Anda menggunakannya terlalu sering. Selama Anda tidak menggunakan * alok dan gratis, dapat dikatakan bahwa Anda masih perawan, dan kodenya masih terlihat bagus.
sumber
Tentu. Jika Anda membuat objek yang ada di luar cakupan tempat Anda menggunakannya. Berikut adalah contoh yang dibuat-buat (ingat sintaks saya akan mati; C saya berkarat, tetapi contoh ini masih akan menggambarkan konsepnya):
class MyClass { SomeOtherClass *myObject; public MyClass() { //The object is created when the class is constructed myObject = (SomeOtherClass*)malloc(sizeof(myObject)); } public ~MyClass() { //The class is destructed //If you don't free the object here, you leak memory free(myObject); } public void SomeMemberFunction() { //Some use of the object myObject->SomeOperation(); } };
Dalam contoh ini, saya menggunakan objek berjenis SomeOtherClass selama masa pakai MyClass. Objek SomeOtherClass digunakan dalam beberapa fungsi, jadi saya telah mengalokasikan memori secara dinamis: objek SomeOtherClass dibuat saat MyClass dibuat, digunakan beberapa kali selama masa pakai objek, dan kemudian dibebaskan setelah MyClass dibebaskan.
Jelas jika ini adalah kode nyata, tidak akan ada alasan (selain dari kemungkinan konsumsi memori tumpukan) untuk membuat myObject dengan cara ini, tetapi jenis pembuatan / penghancuran objek ini menjadi berguna ketika Anda memiliki banyak objek, dan ingin mengontrol dengan cermat ketika dibuat dan dihancurkan (sehingga aplikasi Anda tidak menyedot 1GB RAM untuk seluruh masa pakainya, misalnya), dan dalam lingkungan Windowed, ini cukup wajib, sebagai objek yang Anda buat (tombol, katakanlah) , harus ada dengan baik di luar cakupan fungsi tertentu (atau bahkan kelas ').
sumber