Arduino dünyasında kullanımı malloc()
ve free()
oldukça nadir görünüyor. Saf AVR C'de çok daha sık kullanılır, ancak yine de dikkatli kullanılır.
Kullanımda gerçekten kötü bir fikir mi malloc()
ve free()
Arduino ile?
Arduino dünyasında kullanımı malloc()
ve free()
oldukça nadir görünüyor. Saf AVR C'de çok daha sık kullanılır, ancak yine de dikkatli kullanılır.
Kullanımda gerçekten kötü bir fikir mi malloc()
ve free()
Arduino ile?
Yanıtlar:
Gömülü sistemler için genel malloc()
kuralım yalnızca programın başlangıcında, örn setup()
. Bellek ayırıp ayırdığınızda sorun ortaya çıkar. Uzun süreli bir oturumda, bellek parçalanır ve sonunda toplam serbest bellek istek için yeterli olandan fazla olmasına rağmen, yeterince büyük bir boş alan olmamasından dolayı bir ayırma başarısız olur.
(Tarihsel bakış açısı, ilgilenmiyorsanız atlayın): Yükleyici uygulamasına bağlı olarak, çalışma zamanı tahsisine karşı derleme zamanı tahsisine (intialized globals) göre tek avantaj hex dosyasının boyutudur. Gömülü sistemler tüm geçici belleğe sahip raf dışı bilgisayarlar ile oluşturulduğunda, program genellikle bir ağdan veya bir enstrümantasyon bilgisayarından gömülü sisteme yüklendi ve yükleme süresi bazen bir sorun oldu. Görüntüdeki sıfırlarla dolu tamponların bırakılması, süreyi önemli ölçüde kısaltabilir.)
Gömülü bir sistemde dinamik bellek tahsisine ihtiyacım olursa, genellikle malloc()
veya tercihen statik olarak büyük bir havuzu ayırırım ve sabit boyutlu arabelleklere (ya da sırasıyla küçük ve büyük arabelleklerin her biri için bir havuza) bölerim ve kendi tahsisatımı / bu havuzdan tahsisat. Ardından, sabit arabellek boyutuna kadar olan herhangi bir bellek miktarına yönelik her talep, bu arabelleklerden biri ile onurlandırılır. Çağıran fonksiyonun istenenden daha büyük olup olmadığını bilmek gerekmez ve blokları bölmek ve yeniden birleştirmekle parçalanmayı çözeriz. Tabii ki, program hataları / tahsisatları tahsis etmişse, hafıza sızıntıları hala meydana gelebilir.
Arduino çizimler yazarken Genellikle, sen (ile olsun dinamik ayırmayı önlemek olacaktır malloc
ya da new
C ++ örnekleri için), insan yerine küresel -OR kullanmak static
değişkenleri veya yerel (yığın) değişkenleri -.
Dinamik ayırma kullanmak birkaç soruna yol açabilir:
malloc
/ free
çağrıdan sonra ) Yığın şu anda tahsis edilen hafıza miktarını artırdıkça büyür.Karşılaştığım çoğu durumda, dinamik kodlama ya gerekli değildi ya da aşağıdaki kod örneğindeki gibi makrolarla önlenebilirdi:
MySketch.ino
#define BUFFER_SIZE 32
#include "Dummy.h"
Dummy.h
class Dummy
{
byte buffer[BUFFER_SIZE];
...
};
Olmadan #define BUFFER_SIZE
istediğimiz takdirde, Dummy
sabit olmayan bir olması sınıfını buffer
boyutunu aşağıdaki gibi biz dinamik ayırmayı kullanmak gerekir:
class Dummy
{
const byte* buffer;
public:
Dummy(int size):buffer(new byte[size])
{
}
~Dummy()
{
delete [] bufer;
}
};
Bu durumda, ilk örnekten daha fazla seçeneğe sahibiz (örneğin , her biri için Dummy
farklı buffer
boyutta farklı nesneler kullanın ), ancak yığın parçalanma sorunları yaşayabiliriz.
buffer
Bir Dummy
örnek silindiğinde dinamik olarak ayrılan hafızanın serbest bırakılacağından emin olmak için bir yıkıcı kullanıldığına dikkat edin .
malloc()
Avr-libc'den kullanılan algoritmaya baktım ve yığın parçalanması açısından güvenli olan birkaç kullanım şekli var gibi gözüküyor:
Bununla demek istediğim: Programın başında ihtiyacınız olan her şeyi tahsis edin ve asla serbest bırakmayın. Elbette, bu durumda, statik tamponları da kullanabilirsiniz.
Anlamı: Başka bir şey ayırmadan önce tamponu serbest bırakırsınız. Makul bir örnek şöyle görünebilir:
void foo()
{
size_t size = figure_out_needs();
char * buffer = malloc(size);
if (!buffer) fail();
do_whatever_with(buffer);
free(buffer);
}
İçinde herhangi bir malloc do_whatever_with()
yoksa veya bu fonksiyon tahsis ettiği şeyi serbest bırakırsa, parçalanmaya karşı güvende olursunuz.
Bu, önceki iki vakanın bir genellemesidir. Eğer yığını bir yığın gibi kullanırsanız (son giren ilk çıkar), o zaman bir yığın gibi davranır ve parçalanmaz. Bu durumda, son ayrılan tamponu yeniden boyutlandırmanın güvenli olduğu unutulmamalıdır realloc()
.
Bu parçalanmayı engellemeyecek, ancak yığının azami kullanılan boyuttan daha büyük büyümemesi açısından güvenlidir . Eğer tüm tamponlarınız aynı boyuta sahipse, bunlardan birini serbest bıraktığınızda, yuvanın sonraki tahsisler için uygun olacağından emin olabilirsiniz.
(Kullanarak dinamik ayırma kullanarak malloc
/ free
veya new
/ delete
) gibi doğal olarak kötü değildir. Aslında, dize işleme gibi bir şey için (örneğin, String
nesne üzerinden ), genellikle oldukça yardımcı olur. Çünkü birçok çizim sonunda daha büyük bir hale getirilen birkaç küçük tel parçası kullanır. Dinamik ayırmayı kullanmak, her biri için yalnızca ihtiyaç duyduğunuz kadar belleği kullanmanıza olanak tanır. Buna karşılık, her biri için sabit boyutlu bir statik tampon kullanmak, tamamen içeriğe bağlı olmasına rağmen, çok fazla alan boşa harcayabilir (belleğin çok daha hızlı çalışmasına neden olabilir).
Tüm söylenenlerle birlikte, bellek kullanımının tahmin edilebilir olduğundan emin olmak çok önemlidir. Çizimin çalışma zamanı koşullarına (örn. Giriş) bağlı olarak isteğe bağlı miktarda bellek kullanmasına izin vermek, er ya da geç kolayca bir soruna neden olabilir. Bazı durumlarda, tamamen güvenli olabilir, örneğin kullanımın hiçbir zaman çok fazla katkı sağlayamayacağını biliyorsanız . Eskizler programlama sürecinde olsa değişebilir. Erken yapılan bir varsayım, daha sonra bir şey değiştiğinde unutularak, öngörülemeyen bir sorunla sonuçlanabilir.
Sağlamlık için, mümkünse sabit boyutlu tamponlarla çalışmak ve çizimin başlangıçtan itibaren bu sınırlarla açıkça çalışacak şekilde tasarlanması daha iyidir. Bu, eskizde yapılacak gelecekteki herhangi bir değişikliğin veya beklenmeyen bir çalışma zamanı koşullarının umarım herhangi bir hafıza sorununa yol açmaması gerektiği anlamına gelir.
Kullanmaman gerektiğini düşünen insanlara katılmıyorum ya da genellikle gereksiz. İçinde ve dışında kalanları bilmemenizin tehlikeli olabileceğine inanıyorum, ancak yararlıdır. Özellikle bir dünyaya gönderdiğim kütüphaneler söz konusu olduğunda bir yapının veya arabellek boyutunu (derleme zamanında veya çalışma zamanında) bilmediğim (ve bilmemeye dikkat etmemeli) durumlarım var. Uygulamanızın yalnızca bilinen tek bir yapıyla ilgileniyor olması halinde derleme zamanında bu boyutta pişirmeniz gerektiğini kabul ediyorum.
Örnek: İsteğe bağlı uzunluktaki veri yüklerini alabilen bir seri paket sınıfım (kitaplık) var (yapı olabilir, uint16_t dizisi, vs.). Bu sınıfın gönderen ucunda, Packet.send () yöntemine, göndermek istediğiniz şeyin adresini ve içinden göndermek istediğiniz Donanım Seri portunu söylemeniz yeterlidir. Bununla birlikte, alıcı tarafta, gelen yükü tutmak için dinamik olarak tahsis edilmiş bir alma tamponuna ihtiyacım var, çünkü bu yük, örneğin uygulamanın durumuna bağlı olarak herhangi bir anda farklı bir yapı olabilir. Sadece ileri geri tek bir yapı gönderirsem, arabelleği derleme zamanında olması gereken boyutta yapardım. Ancak, paketlerin zaman içinde farklı uzunluklarda olabileceği durumlarda, malloc () ve free () çok kötü değildir.
Devamlı döngü yapması için günlerce aşağıdaki kodla testler yaptım ve hiçbir bellek parçalanma kanıtı bulamadım. Dinamik olarak ayrılmış hafızayı boşalttıktan sonra, boş miktar önceki değerine döner.
// 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
}
RAM'de veya bu yöntemi kullanarak dinamik olarak tahsis etme yeteneğimde herhangi bir bozulma görmedim, bu yüzden uygun bir araç olduğunu söyleyebilirim. FWIW.
Arduino ile malloc () ve free () kullanmak gerçekten kötü bir fikir mi?
Kısa cevap evet. Nedenler aşağıdadır:
Her şey bir ÇBU'nun ne olduğunu ve mevcut kaynakların kısıtları dahilinde nasıl programlandığını anlamakla ilgilidir. Arduino Uno, 32KB ISP flash bellek, 1024B EEPROM ve 2KB SRAM ile ATmega328p MPU kullanıyor . Bu çok fazla hafıza kaynağı değil.
Unutmayın, 2KB SRAM tüm global değişkenler, string değişmezleri, stack ve yığının olası kullanımı için kullanılır. Yığının ayrıca bir ISR için ana alana sahip olması gerekir.
Bellek düzeni geçerli:
Bugünün PC / dizüstü bilgisayarları, bellek miktarının 1.000.000 katından fazladır. Bir iş parçacığı başına 1 Mbyte varsayılan yığın alanı nadir değildir, ancak bir MPU'da tamamen gerçekçi değildir.
Gömülü bir yazılım projesi bir kaynak bütçesi yapmak zorundadır. Bu ISR gecikmesini, gerekli bellek alanını, hesaplama gücünü, komut döngüsünü vb. Tahmin ediyor. Maalesef ücretsiz öğle yemeği yok ve zor gerçek zamanlı gömülü programlama, programlama becerilerinde ustalaşmak için en zor olanı.
Tamam, bunun eski bir soru olduğunu biliyorum, ancak cevapları ne kadar çok okursam o kadar belirgin görünen bir gözlem yapmaya devam ediyorum.
Turing'in Durma Sorunu ile burada bir bağlantı var gibi görünüyor. Dinamik tahsise izin vermek, “durma” olasılığını arttırır, böylece soru risk toleransı haline gelir. malloc()
Başarısızlık ihtimalini ortadan kaldırmak uygun olsa da, hala geçerli bir sonuçtur. OP'nin sorduğu soru sadece teknikle ilgili görünüyor ve evet, kullanılan kütüphanelerin veya belirli MPU’ların ayrıntıları önemli; konuşma, programın durması veya herhangi bir anormal sonun ortaya çıkma riskini azaltma yönünde döner. Riski çok farklı şekilde tolere eden ortamların varlığını farketmemiz gerekir. Bir LED şeridinde güzel renkler sergilemek için yaptığım hobi projem, olağandışı bir şey olursa birini öldürmez, ancak kalp-akciğer makinesinin içindeki MCU muhtemelen olur.
LED şeridi için kilitlenip kilitlenmediği umrumda değil, sadece sıfırlayacağım. Bunun sonuçları kapısını açıp kapatmak işletmek için başarısız bir MCU tarafından kontrol edilen bir kalp-akciğer makinesi üzerinde olsaydı, tam anlamıyla yaşam ve ölüm böylece ilgili soru malloc()
ve free()
Mr. gösteren imkanı ile nasıl amaçlanan programı anlaşmaları arasındaki bölünmeyi olmalıdır Turing'in ünlü sorunu. Bunun matematiksel bir kanıt olduğunu unutmak ve kendimizi ancak yeterince zekiysek hesaplama sınırlarının zayiatından kaçınabileceğimize ikna etmek kolay olabilir.
Bu sorunun, biri Halting Sorunu'na bakarken, diğeri de diğerleri için yanıp sönmeye zorlananlar için kabul edilen iki yanıtı olmalıdır. Arduino'nun çoğu kullanımı büyük olasılıkla kritik görev veya yaşam ve ölüm uygulamaları olmasa da, hangi MPU'yu kodladığınızdan bağımsız olarak, ayrım hala oradadır.
Hayır, ancak tahsis edilen hafızaya alma () konusunda çok dikkatli kullanılmaları gerekir. İnsanların neden doğrudan bellek yönetiminden kaçınılması gerektiğini asla anlamadım, çünkü genellikle yazılım geliştirme ile bağdaşmayan bir yetersizlik düzeyi anlamına geliyor.
Arduino'nuzu bir dronu kontrol etmek için kullandığınızı söyleyelim. Kodunuzun herhangi bir kısmındaki herhangi bir hata potansiyel olarak gökyüzünden düşmesine ve birisine veya başka bir şeye zarar vermesine neden olabilir. Başka bir deyişle, eğer biri malloc kullanma yeterliliğine sahip değilse, küçük böceklerin ciddi sorunlara neden olabileceği pek çok başka alan olduğu için muhtemelen kodlamamalıdırlar.
Malloc'un neden olduğu böceklerin izini sürmesi ve düzeltmesi daha mı zor? Evet, fakat kodlayıcılar açısından riskten çok bir hayal kırıklığı meselesi. Risk söz konusu olduğunda, doğru yapıldığından emin olmak için gerekli adımları atmazsanız, kodunuzun herhangi bir kısmı malloc'tan eşit veya daha riskli olabilir.