Penggunaan malloc()
dan free()
tampaknya cukup langka di dunia Arduino. Ini digunakan dalam AVR C murni jauh lebih sering, tetapi masih dengan hati-hati.
Apakah ini ide yang sangat buruk untuk digunakan malloc()
dan free()
dengan Arduino?
Penggunaan malloc()
dan free()
tampaknya cukup langka di dunia Arduino. Ini digunakan dalam AVR C murni jauh lebih sering, tetapi masih dengan hati-hati.
Apakah ini ide yang sangat buruk untuk digunakan malloc()
dan free()
dengan Arduino?
Jawaban:
Aturan umum saya untuk sistem embedded adalah hanya malloc()
buffer besar dan hanya sekali, pada awal program, misalnya di setup()
. Masalahnya muncul ketika Anda mengalokasikan dan mendelegasikan memori. Selama sesi jangka panjang, memori menjadi terfragmentasi dan akhirnya alokasi gagal karena kurangnya area bebas yang cukup besar, meskipun total memori bebas lebih dari cukup untuk permintaan.
(Perspektif historis, lewati jika tidak tertarik): Bergantung pada implementasi loader, satu-satunya keuntungan dari alokasi run-time vs alokasi waktu kompilasi (globalisasi global) adalah ukuran file hex. Ketika sistem tertanam dibangun dengan komputer yang memiliki semua memori tidak stabil, program sering diunggah ke sistem tertanam dari jaringan atau komputer instrumentasi dan waktu pengunggahan terkadang menjadi masalah. Meninggalkan buffer yang penuh dengan nol dari gambar dapat mempersingkat waktu.)
Jika saya membutuhkan alokasi memori dinamis dalam sistem tertanam, saya umumnya malloc()
, atau lebih disukai, mengalokasikan secara statis, kumpulan besar dan membaginya menjadi buffer ukuran tetap (atau masing-masing satu kumpulan buffer kecil dan besar, masing-masing) dan melakukan alokasi saya sendiri / de-alokasi dari kumpulan itu. Kemudian setiap permintaan untuk jumlah memori berapa pun hingga ukuran buffer tetap dihormati dengan salah satu buffer itu. Fungsi panggilan tidak perlu tahu apakah itu lebih besar dari yang diminta, dan dengan menghindari pemisahan dan penggabungan ulang blok kami memecahkan fragmentasi. Tentu saja kebocoran memori masih dapat terjadi jika program telah mengalokasikan / menghapus alokasi bug.
Biasanya, saat menulis sketsa Arduino, Anda akan menghindari alokasi dinamis (baik dengan malloc
atau new
untuk contoh C ++), orang lebih suka menggunakan static
variabel global atau variabel, atau variabel lokal (tumpukan).
Menggunakan alokasi dinamis dapat menyebabkan beberapa masalah:
malloc
/ free
panggilan) di mana heap tumbuh lebih besar dari jumlah sebenarnya memori yang dialokasikan saat iniDalam sebagian besar situasi yang saya hadapi, alokasi dinamis tidak diperlukan, atau dapat dihindari dengan makro seperti dalam contoh kode berikut:
MySketch.ino
#define BUFFER_SIZE 32
#include "Dummy.h"
Dummy.h
class Dummy
{
byte buffer[BUFFER_SIZE];
...
};
Tanpa #define BUFFER_SIZE
, jika kita ingin Dummy
kelas memiliki ukuran yang tidak tetap buffer
, kita harus menggunakan alokasi dinamis sebagai berikut:
class Dummy
{
const byte* buffer;
public:
Dummy(int size):buffer(new byte[size])
{
}
~Dummy()
{
delete [] bufer;
}
};
Dalam hal ini, kami memiliki lebih banyak opsi daripada sampel pertama (misalnya menggunakan Dummy
objek yang berbeda dengan buffer
ukuran yang berbeda untuk masing-masing), tetapi kami mungkin memiliki masalah tumpukan fragmentasi.
Perhatikan penggunaan destruktor untuk memastikan memori yang dialokasikan secara dinamis untuk buffer
akan dibebaskan ketika Dummy
instance dihapus.
Saya telah melihat algoritma yang digunakan oleh malloc()
, dari avr-libc, dan tampaknya ada beberapa pola penggunaan yang aman dari sudut pandang tumpukan fragmentasi:
Maksud saya: alokasikan semua yang Anda butuhkan di awal program, dan jangan pernah membebaskannya. Tentu saja, dalam hal ini, Anda juga dapat menggunakan buffer statis ...
Artinya: Anda membebaskan buffer sebelum mengalokasikan apa pun. Contoh yang masuk akal mungkin terlihat seperti ini:
void foo()
{
size_t size = figure_out_needs();
char * buffer = malloc(size);
if (!buffer) fail();
do_whatever_with(buffer);
free(buffer);
}
Jika tidak ada malloc di dalam do_whatever_with()
, atau jika fungsi itu membebaskan apa pun yang dialokasikan, maka Anda aman dari fragmentasi.
Ini adalah generalisasi dari dua kasus sebelumnya. Jika Anda menggunakan tumpukan seperti tumpukan (masuk pertama keluar), maka ia akan berperilaku seperti tumpukan dan bukan fragmen. Perlu dicatat bahwa dalam hal ini aman untuk mengubah ukuran buffer yang dialokasikan terakhir realloc()
.
Ini tidak akan mencegah fragmentasi, tetapi aman dalam arti bahwa tumpukan tidak akan tumbuh lebih besar dari ukuran maksimum yang digunakan . Jika semua buffer Anda memiliki ukuran yang sama, Anda dapat yakin bahwa, kapan pun Anda membebaskannya, slot akan tersedia untuk alokasi selanjutnya.
Menggunakan alokasi dinamis (via malloc
/ free
atau new
/ delete
) pada dasarnya tidak seburuk itu. Bahkan, untuk sesuatu seperti pemrosesan string (misalnya melalui String
objek), seringkali cukup membantu. Itu karena banyak sketsa menggunakan beberapa fragmen kecil string, yang akhirnya digabungkan menjadi yang lebih besar. Menggunakan alokasi dinamis memungkinkan Anda menggunakan hanya memori sebanyak yang Anda butuhkan untuk masing-masing memori. Sebaliknya, menggunakan buffer statis ukuran tetap untuk masing-masingnya dapat menghabiskan banyak ruang (menyebabkan kehabisan memori lebih cepat), meskipun itu sepenuhnya tergantung pada konteksnya.
Dengan semua itu dikatakan, sangat penting untuk memastikan penggunaan memori dapat diprediksi. Mengizinkan sketsa untuk menggunakan jumlah memori sewenang-wenang tergantung pada kondisi run-time (misalnya input) dapat dengan mudah menyebabkan masalah cepat atau lambat. Dalam beberapa kasus, ini mungkin sangat aman, misalnya jika Anda tahu penggunaannya tidak akan bertambah banyak. Sketsa dapat berubah selama proses pemrograman. Asumsi yang dibuat sejak awal bisa dilupakan ketika sesuatu diubah kemudian, menghasilkan masalah yang tidak terduga.
Untuk ketahanan, biasanya lebih baik bekerja dengan buffer ukuran tetap jika memungkinkan, dan merancang sketsa untuk bekerja secara eksplisit dengan batas-batas tersebut sejak awal. Itu berarti setiap perubahan sketsa di masa depan, atau keadaan run-time yang tidak terduga, semoga tidak menyebabkan masalah memori.
Saya tidak setuju dengan orang-orang yang berpikir Anda tidak boleh menggunakannya atau itu umumnya tidak perlu. Saya percaya ini bisa berbahaya jika Anda tidak mengetahui seluk beluknya, tetapi ini berguna. Saya memang memiliki kasus di mana saya tidak tahu (dan seharusnya tidak peduli untuk mengetahui) ukuran struktur atau buffer (pada waktu kompilasi atau run time), terutama ketika datang ke perpustakaan yang saya kirim ke dunia. Saya setuju bahwa jika aplikasi Anda hanya berurusan dengan struktur tunggal yang diketahui, Anda harus memanggang ukuran itu pada waktu kompilasi.
Contoh: Saya memiliki kelas paket serial (pustaka) yang dapat mengambil payload data panjang sewenang-wenang (bisa berupa struct, array dari uint16_t, dll.). Pada akhir pengiriman kelas itu, Anda cukup memberi tahu metode Packet.send () alamat benda yang ingin Anda kirim dan port HardwareSerial tempat Anda ingin mengirimkannya. Namun, pada sisi penerima saya membutuhkan buffer penerima yang dialokasikan secara dinamis untuk menahan muatan yang masuk, karena muatan itu bisa menjadi struktur yang berbeda pada saat tertentu, misalnya, tergantung pada kondisi aplikasi. JIKA saya hanya pernah mengirim struktur tunggal bolak-balik, saya hanya akan membuat buffer ukuran yang dibutuhkan pada waktu kompilasi. Tetapi, dalam kasus di mana paket mungkin berbeda panjang dari waktu ke waktu, malloc () dan free () tidak terlalu buruk.
Saya sudah menjalankan tes dengan kode berikut selama berhari-hari, membiarkannya berulang terus, dan saya tidak menemukan bukti fragmentasi memori. Setelah membebaskan memori yang dialokasikan secara dinamis, jumlah bebas kembali ke nilai sebelumnya.
// found at learn.adafruit.com/memories-of-an-arduino/measuring-free-memory
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
uint8_t *_tester;
while(1) {
uint8_t len = random(1, 1000);
Serial.println("-------------------------------------");
Serial.println("len is " + String(len, DEC));
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("alloating _tester memory");
_tester = (uint8_t *)malloc(len);
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
Serial.println("Filling _tester");
for (uint8_t i = 0; i < len; i++) {
_tester[i] = 255;
}
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("freeing _tester memory");
free(_tester); _tester = NULL;
Serial.println("RAM: " + String(freeRam(), DEC));
Serial.println("_tester = " + String((uint16_t)_tester, DEC));
delay(1000); // quick look
}
Saya belum melihat adanya penurunan dalam RAM atau dalam kemampuan saya untuk mengalokasikannya secara dinamis menggunakan metode ini, jadi saya akan mengatakan itu adalah alat yang layak. FWIW.
Apakah itu ide yang sangat buruk untuk menggunakan malloc () dan gratis () dengan Arduino?
Jawaban singkatnya adalah ya. Berikut alasannya:
Ini semua tentang memahami apa itu MPU dan bagaimana memprogram dalam batasan sumber daya yang tersedia. The Arduino Uno menggunakan MPU ATmega328p dengan memori flash ISP 32KB, EEPROM 1024B, dan SRAM 2KB. Itu tidak banyak sumber daya memori.
Ingat bahwa SRAM 2KB digunakan untuk semua variabel global, string literal, stack, dan kemungkinan penggunaan heap. Tumpukan juga perlu memiliki ruang kepala untuk ISR.
The tata letak memori adalah:
Saat ini PC / laptop memiliki lebih dari 1.000.000 kali jumlah memori. Ruang stack default 1 Mbyte per utas tidak jarang tetapi sama sekali tidak realistis pada MPU.
Proyek perangkat lunak tertanam harus melakukan anggaran sumber daya. Ini memperkirakan latensi ISR, ruang memori yang diperlukan, daya hitung, siklus instruksi, dll. Sayangnya tidak ada makan siang gratis dan pemrograman tersemat real-time adalah keterampilan pemrograman yang paling sulit untuk dikuasai.
Ok, saya tahu ini adalah pertanyaan lama tetapi semakin saya membaca jawaban semakin saya terus kembali ke pengamatan yang tampaknya menonjol.
Tampaknya ada tautan dengan Masalah Puting Turing di sini. Mengizinkan alokasi dinamis meningkatkan peluang kata 'terhenti' sehingga pertanyaannya menjadi toleransi risiko. Meskipun nyaman untuk menghilangkan kemungkinan malloc()
gagal dan sebagainya, itu masih merupakan hasil yang valid. Pertanyaan yang ditanyakan OP hanya nampak tentang teknik, dan ya detail perpustakaan yang digunakan atau MPU spesifik memang penting; percakapan berubah ke arah mengurangi risiko terhentinya program atau akhir yang tidak normal lainnya. Kita perlu mengenali keberadaan lingkungan yang mentolerir risiko sangat berbeda. Proyek hobi saya untuk menampilkan warna-warna cantik pada strip-LED tidak akan membunuh seseorang jika sesuatu yang tidak biasa terjadi tetapi MCU di dalam mesin jantung-paru kemungkinan akan melakukannya.
Untuk strip-LED saya, saya tidak peduli jika terkunci, saya hanya akan mengatur ulang. Jika saya menggunakan mesin jantung-paru yang dikendalikan oleh MCU, konsekuensi dari pengunciannya atau gagal beroperasi adalah benar-benar hidup dan mati, jadi pertanyaan tentang malloc()
dan free()
harus dipisah antara bagaimana program yang dimaksud menangani kemungkinan menunjukkan Tn. Masalah terkenal Turing. Mungkin mudah untuk melupakan bahwa itu adalah bukti matematis dan meyakinkan diri kita bahwa jika kita cukup pintar kita dapat menghindari menjadi korban dari batas perhitungan.
Pertanyaan ini seharusnya memiliki dua jawaban yang diterima, satu untuk mereka yang dipaksa untuk berkedip ketika menatap Masalah Henti di wajah, dan satu untuk yang lainnya. Sementara sebagian besar penggunaan arduino kemungkinan bukan misi kritis atau aplikasi hidup dan mati, perbedaannya masih ada terlepas dari MPU mana yang Anda kodekan.
Tidak, tetapi mereka harus digunakan dengan sangat hati-hati dalam hal membebaskan () memori yang dialokasikan. Saya tidak pernah mengerti mengapa orang mengatakan manajemen memori langsung harus dihindari karena ini menyiratkan tingkat ketidakmampuan yang umumnya tidak sesuai dengan pengembangan perangkat lunak.
Katakanlah Anda menggunakan Arduino Anda untuk mengendalikan drone. Kesalahan apa pun di bagian mana pun dari kode Anda berpotensi menyebabkannya jatuh dari langit dan melukai seseorang atau sesuatu. Dengan kata lain, jika seseorang tidak memiliki kompetensi untuk menggunakan malloc, mereka kemungkinan tidak boleh mengkode sama sekali karena ada begitu banyak area lain di mana bug kecil dapat menyebabkan masalah serius.
Apakah bug yang disebabkan oleh malloc lebih sulit dilacak dan diperbaiki? Ya, tapi itu lebih merupakan masalah frustrasi pada bagian coders daripada risiko. Sejauh risiko berjalan, setiap bagian dari kode Anda dapat sama atau lebih berisiko daripada malloc jika Anda tidak mengambil langkah-langkah untuk memastikan itu dilakukan dengan benar.