Malloc () ve free () kullanmak Arduino hakkında gerçekten kötü bir fikir mi?


49

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?


2
Eğer gerçekten hızlı aksi bellek tükendi edeceğiz ve siz de statik olarak yine de ayırabilir size kullanacağız ne kadar bellek biliyorsanız
mandal ucube

1
Kötü olup olmadığını bilmiyorum , ama bence kullanılmıyor çünkü çoğu skeç için neredeyse hiç RAM tükenmiyor ve bu sadece bir flaş ve değerli saat devri kaybı. Ayrıca, kapsamı unutma (bu alanın hala tüm değişkenler için ayrılmış olup olmadığını bilmeme rağmen).
Anonim Penguen

4
Her zamanki gibi, doğru cevap "bağlıdır". Dinamik ayırmanın sizin için uygun olup olmadığından emin olmak için yeterli bilgi vermediniz.
WineSoaked

Yanıtlar:


40

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.


Başka bir tarihsel not, bu hızlı bir şekilde program yükü sırasında sıfırları yavaşça kopyalamaksızın bir programın başlatma için kendi belleğini sıfırlamasına izin veren BSS segmentine yol açtı.
rsaxvc

16

Arduino çizimler yazarken Genellikle, sen (ile olsun dinamik ayırmayı önlemek olacaktır mallocya da newC ++ örnekleri için), insan yerine küresel -OR kullanmak staticdeğişkenleri veya yerel (yığın) değişkenleri -.

Dinamik ayırma kullanmak birkaç soruna yol açabilir:

  • bellek sızıntısı (önceden tahsis ettiğiniz hafızaya bir işaretçi kaybederseniz veya daha fazla ihtiyacınız olmadığında tahsis edilen hafızayı boşaltmayı unutursanız daha büyük olasılıkla)
  • Yığın parçalanması (birkaç kez 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_SIZEistediğimiz takdirde, Dummysabit olmayan bir olması sınıfını bufferboyutunu 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 Dummyfarklı bufferboyutta farklı nesneler kullanın ), ancak yığın parçalanma sorunları yaşayabiliriz.

bufferBir 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 .


14

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:

1. Sadece uzun ömürlü tamponları dağıtın

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.

2. Sadece kısa ömürlü tamponları dağıtın

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.

3. Son tahsis edilen tamponu daima serbest bırakın

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().

4. Her zaman aynı boyuta ayır

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.


1
Desen 2, "char buffer [size]; ile yapılabildiğinde malloc () ve free () için döngü eklediğinden kaçınılmalıdır. (C ++ 'da). Ayrıca "Asla ISR'den" anti-paterni eklemek isterim.
Mikael Patel

9

(Kullanarak dinamik ayırma kullanarak malloc/ freeveya new/ delete) gibi doğal olarak kötü değildir. Aslında, dize işleme gibi bir şey için (örneğin, Stringnesne ü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.


6

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.


2
Test kodunuz kullanım düzenine ( 2) uygundur. Sadece önceki cevabımda tanımladığım kısa ömürlü tamponları ayırın . Bu, güvenli olduğu bilinen az sayıdaki kullanım modelinden biridir.
Edgar Bonet

Başka bir deyişle, işlemciyi diğer bilinmeyen kodlarla paylaşmaya başladığınızda sorunlar ortaya çıkacaktır - bu tam olarak kaçındığınızı düşündüğünüz problemdir. Genel olarak, bağlantı sırasında her zaman çalışacak veya başarısız olacak bir şey istiyorsanız, maksimum boyutun sabit bir tahsisatını yaparsınız ve örneğin, başlatma sırasında size vermesini sağlamak için tekrar tekrar kullanırsınız. Genellikle her şeyin 2048 bayta sığması gereken bir yonga üzerinde çalıştığınızı unutmayın - belki bazı panolarda daha fazla, belki de diğerlerinde daha az.
Chris Stratton

@EdgarBonet Evet, kesinlikle. Sadece paylaşmak istedim.
StuffAndyMakes 19:15

1
Dinamik olarak sadece gerekli boyutta bir tampon ayırmak risklidir, sanki serbest bırakmadan önce başka bir şey ayırmış gibi parçalanmaya bırakılabilir - tekrar kullanamayacağınız bir bellek. Ayrıca, dinamik ayırma ek yükü vardır. Sabit tahsisat, hafızayı çoğaltamayacağınız anlamına gelmez, bu sadece paylaşımı programınızın tasarımında çalışmanız gerektiği anlamına gelir. Tamamen yerel kapsama sahip bir tampon için, yığının kullanımını da ölçebilirsiniz. Malloc () 'un da başarısız olma ihtimalini kontrol etmediniz.
Chris Stratton

1
"Eğer onun içini ve dışını bilmiyorsanız, tehlikeli olabilir, ancak yararlıdır." hemen hemen tüm gelişmeleri C / C ++ ile özetliyor. :-)
ThatAintWorking

4

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:

SRAM haritası

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ı.


Amin: “[H] gerçek zamanlı gömülü programlama, programlama becerilerini geliştirmek için en zorudur.”
StuffAndyMakes 17:17 de 17:17

Malloc'un yürütme süresi her zaman aynı mıdır? Malloc'un uygun ramda daha fazla yer bulması için daha fazla zaman harcadığını hayal edebiliyorum. Bu, halindeyken bellek ayırmamak için başka bir argüman (ram dışında) bir daha olur mu?
Paul

@Paul Yığın algoritmaları (malloc ve serbest) tipik olarak sabit yürütme süresi değildir ve yinelemeli değildir. Algoritma, iş parçacığı kullanırken (eşzamanlılık) kilitleme gerektiren arama ve veri yapılarını içerir.
Mikael Patel

0

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.

Durma Sorunu Gerçek mi

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.

Merhaba, Bay Turing Adım Hubris

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.


Halting probleminin, yığın kullanımının mutlaka keyfi olmadığı gerçeğini göz önünde bulundurarak bu özel durumda geçerli olduğunu sanmıyorum. İyi tanımlanmış bir şekilde kullanılırsa, yığın kullanımı tahmin edilebilir şekilde "güvenli" olur. Halting probleminin amacı, mutlaka rastlantısal ve çok iyi tanımlanmamış bir algoritmaya ne olduğu tespit edilip edilmediği tespit edildi. Programlama için daha geniş anlamda gerçekten çok daha fazla geçerlidir ve bu yüzden burada özellikle çok ilgili olmadığını düşünüyorum. Tamamen dürüst olmanın hiç alakalı olduğunu bile sanmıyorum.
Jonathan Gray

Bazı retorik abartılara itiraf edeceğim ama asıl amaç, davranışı güvence altına almak istiyorsanız, yığını kullanmak, yalnızca yığını kullanmaktan çok daha yüksek bir risk düzeyi anlamına gelir.
Kelly S. French

-3

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.


4
Örnek olarak bir drone kullanmanız ilginç. Bu makaleye göre ( mil-embedded.com/articles/… ), “Risk nedeniyle DO-178B standardı altında güvenlik açısından kritik gömülü aviyonik kodda dinamik bellek tahsisi yasaktır.”
Gabriel Staples

DARPA, yüklenicilerin kendi platformlarına uygun özellikleri geliştirmelerine izin verme konusunda uzun bir geçmişe sahiptir - neden faturayı ödeyen vergi mükellefleri olsa olmasınlar. Bu nedenle başkalarının 10.000 $ ile yapabileceklerini geliştirmeleri 10 milyar dolara mal oluyor. Askeri sanayi kompleksini dürüst bir referans olarak kullanıyorsanız neredeyse kulağa hoş geliyor.
JSON

Dinamik ayırma, Programınız için Halting Probleminde açıklanan hesaplama sınırlarını gösterme davetiyesine benziyor. Bu tür durma riskini az miktarda karşılayabilecek bazı ortamlar vardır ve kontrol edilebilir herhangi bir riske tolerans göstermeyecek ortamlar (alan, savunma, tıbbi vb.) Vardır, bu nedenle "yapmaması gereken" işlemlere izin vermezler. başarısız olması çünkü 'çalışması gerekir' bir roket fırlatırken veya bir kalp / akciğer makinesini kontrol ederken yeterince iyi değildir.
Kelly S. French,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.