Kysymys:
Onko malloc (): n ja free (): n käyttö todella huono idea Arduinossa?
Cybergibbons
2014-03-09 14:00:25 UTC
view on stackexchange narkive permalink

malloc () - ja free () -muotojen käyttö vaikuttaa melko harvinaiselta Arduino-maailmassa. Sitä käytetään puhtaassa AVR C: ssä paljon useammin, mutta silti varoen.

Onko todella huono idea käyttää malloc () - ja free () Arduinon kanssa?

muisti loppuu muistista todella nopeasti, ja jos tiedät kuinka paljon muistia käytät, voit myös jakaa sen staattisesti
En tiedä onko se * huono *, mutta mielestäni sitä ei käytetä, koska useimmissa luonnoksissa ei melkein koskaan loppu RAM-muistia, ja se on vain salaman ja arvokkaiden kellojaksojen tuhlausta. Älä myöskään unohda laajuutta (vaikka en tiedä, onko tilaa varattu edelleen kaikille muuttujille).
Kuten tavallista, oikea vastaus on "se riippuu". Et ole toimittanut tarpeeksi tietoja tietääksesi varmasti, sopiiko dynaaminen kohdistaminen sinulle.
Kahdeksan vastused:
#1
+43
JRobert
2014-03-11 21:36:25 UTC
view on stackexchange narkive permalink

Sulautettujen järjestelmien yleinen sääntöni on, että vain malloc () -puskurit ja vain kerran ohjelman alussa, esimerkiksi setup () -kohdassa. Ongelma tulee, kun jaat muistin ja varaat muistin. Pitkän aikavälin istunnon aikana muisti pirstaloituu ja lopulta allokointi epäonnistuu riittävän suuren vapaan alueen puuttuessa, vaikka vapaana oleva kokonaismuisti onkin enemmän kuin riittävä pyynnölle.

(Historiallinen perspektiivi, ohita, jos et ole kiinnostunut): Lataimen toteutuksesta riippuen ajonaikaisen allokoinnin ainoa etu verrattuna kääntöaikaan (intialisoidut globaalit) on heksatiedoston koko. Kun sulautettuja järjestelmiä rakennettiin niin, että tietokoneilla oli kaikki haihtuvat muistit, ohjelma ladattiin usein sulautettuun järjestelmään verkosta tai instrumentointitietokoneelta, ja latausaika oli joskus ongelma. Nollista täynnä olevien puskurien jättäminen pois kuvasta voi lyhentää aikaa huomattavasti.)

Jos tarvitsen dynaamista muistin allokointia sulautetussa järjestelmässä, yleensä malloc () tai mieluiten, staattisesti allokoi suuri pooli ja jaa se kiinteäkokoisiin puskureihin (tai yhteen pooliin kumpikin pienistä ja suurista puskureista, vastaavasti) ja teen oman allokoinnin / varauksen purkamisen kyseisestä poolista. Sitten jokainen pyyntö mistä tahansa muistin määrästä kiinteään puskurikokoon saakka täyttyy yhdellä näistä puskureista. Soittotoiminnon ei tarvitse tietää, onko se vaadittua suurempi, ja välttämällä lohkojen jakamisen ja yhdistämisen ratkaisemme pirstoutumisen. Tietysti muistivuotoja voi silti esiintyä, jos ohjelmassa on allokointivirheitä.

Toinen historiallinen huomautus, tämä johti nopeasti BSS-segmenttiin, joka antoi ohjelman nollata oman muistin alustamista varten kopioimatta nollia hitaasti ohjelman lataamisen aikana.
#2
+18
Edgar Bonet
2015-03-01 19:51:45 UTC
view on stackexchange narkive permalink

Olen katsonut malloc () : n käyttämää algoritmia avr-libc: stä, ja näyttää olevan muutamia käyttötapoja, jotka ovat turvallisia kasaantumisen kannalta:

1. Kohdista vain pitkäikäiset puskurit

Tarkoitan tällä: allokoi kaikki tarvitsemasi ohjelman alkuun, älä koskaan vapauta sitä. Tietenkin tässä tapauksessa voit käyttää myös staattisia puskureita ...

2. Määritä vain lyhytaikaiset puskurit

Merkitys: vapautat puskurin ennen kuin allokoit mitään muuta. Areasonable-esimerkki voi näyttää tältä:

  void foo () {size_t size = figure_out_needs (); char * puskuri = malloc (koko); jos (! puskuri) epäonnistuu (); tee_minkä_ kanssa (puskuri); vapaa (puskuri);}  

Jos do_whatever_with () -kohdassa ei ole mallocia tai jos funktio vapauttaa mitä tahansa varaamaansa, olet turvassa pirstoutumiselta.

3. Vapauta aina viimeksi varattu puskuri

Tämä on yleistys kahdesta edellisestä tapauksesta. Jos käytät heaplike-pinoa (viimeinen sisään on ensimmäinen ulos), se käyttäytyy kuin pino eikä fragmentti. On huomattava, että tässä tapauksessa on turvallista toresisoida viimeksi varattu puskuri realloc().

4: llä. Määritä aina sama koko

Tämä ei estä pirstoutumista, mutta on turvallista siinä mielessä, että kasa ei kasva suuremmaksi kuin käytetty enimmäiskoko. Jos kaikilla ostajillasi on sama koko, voit olla varma, että aina kun vapautat yhden niistä, paikka on käytettävissä myöhemmille varauksille.

Kuviota 2 tulisi välttää, koska se lisää malloc () - ja free () -syklejä, kun tämä voidaan tehdä "char-puskurilla [koko]"; (C ++). Haluaisin myös lisätä anti-mallin "Ei koskaan ISR: ltä".
#3
+17
jfpoilpret
2014-03-09 22:07:19 UTC
view on stackexchange narkive permalink

Kun kirjoitat Arduino-luonnoksia, vältät tyypillisesti dynaamisen allokoinnin (olipa kyseessä sitten malloc tai new C ++ -ilmentymissä), ihmiset käyttävät mieluummin globaalia -tai staattinen - muuttujat tai paikalliset (pino) muuttujat.

Dynaamisen allokoinnin käyttö voi johtaa useisiin ongelmiin:

  • muisti vuotaa (jos menetät osoittimen aiemmin määrittämäsi muisti tai todennäköisemmin, jos unohdat vapauttaa varatun muistin, kun et enää tarvitse sitä)
  • kasan pirstaleisuus (useiden malloc / jälkeen ilmaiset puhelut), kun kasa kasvaa suuremmaksi kuin nykyinen allokoidun muistin määrä.

Useimmissa tilanteissa, joissa olen törmännyt, dynaaminen allokointi ei ollut välttämätöntä tai se voitiin välttää makrot kuten seuraavassa koodinäytteessä:

MySketch.ino

  #define BUFFER_SIZE 32 # include "Dummy.h"  

  • Dummy.h
      class Dummy {tavupuskuri [BUFFER_SIZE]; ...};  

    Ilman #define BUFFER_SIZE -ominaisuutta, jos halusimme Dummy -luokassa olevan kiinteän -puskurin koon, meidän on käytettävä dynaamista allokointia seuraavasti:

      class Dummy {const byte * puskuri; public: Dummy (int-koko): puskuri (uusi tavu [size]) {} ~ Dummy () {delete [] bufer; }};  

    Tällöin meillä on enemmän vaihtoehtoja kuin ensimmäisessä näytteessä (esim. käytä erilaisia ​​ Dummy -objekteja, joissa on erilainen -puskuri koon kullekin), mutta meillä voi olla kasan pirstoutumisongelmia.

    Huomaa, että destruktorin käyttö varmistaa, että puskuri : lle dynaamisesti varattu muisti vapautuu, kun Dummy ilmentymä poistetaan.

  • #4
    +9
    Peter Bloomfield
    2014-03-09 23:32:04 UTC
    view on stackexchange narkive permalink

    Dynaamisen allokoinnin ( malloc / free tai new / delete kautta) käyttö ei ole luonnostaan ​​huono sellaisia. Itse asiassa jotain merkkijonon käsittelyä varten (esim. Objektin String kautta), se on usein varsin hyödyllistä. Tämä johtuu siitä, että monissa luonnoksissa käytetään useita pieniä katkelmia jousista, jotka lopulta yhdistyvät suuremmiksi. Dynaamisen kohdistamisen avulla voit käyttää vain niin paljon muistia kuin tarvitset jokaiselle. Sitä vastoin kiinteän koon staattisen puskurin käyttäminen kullekin voi johtaa paljon tilan tuhlaamiseen (jolloin muisti loppuu paljon nopeammin), vaikka se riippuu täysin asiayhteydestä.

    Kaikilla Tästä huolimatta on erittäin tärkeää varmistaa, että muistin käyttö on ennustettavissa. Salliminen, että luonnos käyttää mielivaltaisia ​​määriä muistia ajo-olosuhteista riippuen (esim. Syöttö), voi helposti aiheuttaa ongelman ennemmin tai myöhemmin. Joissakin tapauksissa se voi olla täysin turvallinen, esim. jos tiedät , käyttö ei koskaan lisää paljon. Luonnokset voivat kuitenkin muuttua ohjelmointiprosessin aikana. Varhain tehty oletus voidaan unohtaa, kun jotain muutetaan myöhemmin, mikä johtaa odottamattomaan ongelmaan.

    Vankkuuden vuoksi on yleensä parempi työskennellä kiinteän kokoisten puskureiden kanssa mahdollisuuksien mukaan ja suunnitella luonnos toimimaan nimenomaisesti näiden rajojen kanssa alusta alkaen. Tämä tarkoittaa sitä, että luonnoksen tulevat muutokset tai odottamattomat ajo-olosuhteet eivät toivottavasti saa aiheuttaa muistiongelmia.

    #5
    +8
    StuffAndyMakes
    2015-05-18 19:49:12 UTC
    view on stackexchange narkive permalink

    Olen eri mieltä ihmisten kanssa, joiden mielestä sinun ei pitäisi käyttää sitä tai se on yleensä tarpeetonta. Uskon, että se voi olla vaarallista, jos et tiedä sen vivahteita, mutta se on hyödyllistä. Minulla on tapauksia, joissa en tiedä (eikä minun pitäisi olla väliä tietää) rakenteen tai puskurin kokoa (kokoamis- tai ajoaikana), varsinkin kun kyse on kirjastoista, jotka lähetän maailmaan. Olen samaa mieltä siitä, että jos sovelluksesi käsittelee vain yhtä, tunnettua rakennetta, sinun tulisi vain paistaa siinä koossa käännösaikana.

    Esimerkki: Minulla on sarjapakettiluokka (kirjasto), joka voi ottaa mielivaltaisen pituiset tiedon hyötykuormat (voi olla strukturoitu, uint16_t-taulukko jne.). Kurssin lähettämisosassa kerrot yksinkertaisesti Packet.send () -menetelmällä lähetettävän kohteen osoitteen ja HardwareSerial-portin, jonka kautta haluat lähettää sen. Kuitenkin vastaanottopäässä tarvitsen dynaamisesti allokoidun vastaanottopuskurin pitämään sitä tulevaa hyötykuormaa, koska kyseinen hyötykuorma voi olla erilainen rakenne kulloinkin, sovelluksen tilasta riippuen. JOS lähetän vain yhden rakenteen edestakaisin, tekisin vain puskurin koon, jonka sen on oltava kääntöhetkellä. Mutta jos paketit saattavat olla eri pituisia ajan mittaan, malloc () ja free () eivät ole niin pahoja.

    Olen suorittanut testejä seuraavalla koodilla päivien ajan antamalla sen jatkua jatkuvasti, enkä ole löytänyt todisteita muistin pirstoutumisesta. Dynaamisesti allokoidun muistin vapauttamisen jälkeen vapaa määrä palaa edelliseen arvoonsa.

      // löytyi osoitteesta learn.adafruit.com/memories-of-an-arduino/measuring-free-memoryint 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 ​​= satunnainen (1, 1000); Sarja.println ("-------------------------------------"); Serial.println ("len on" + merkkijono (len, DEC)); Serial.println ("RAM:" + String (freeRam (), DEC));
    Serial.println ("_ tester =" + Merkkijono ((uint16_t) _tester, DEC)); Serial.println ("allokoiva _testimuisti"); _testeri = (uint8_t *) malloc (len); Serial.println ("RAM:" + String (freeRam (), DEC)); Serial.println ("_ tester =" + Merkkijono ((uint16_t) _tester, DEC)); Serial.println ("Täyttö _testi"); for (uint8_t i = 0; i < len; i ++) {_testeri [i] = 255; } Serial.println ("RAM:" + Merkkijono (freeRam (), DEC)); Serial.println ("vapauttaa _testimuistia"); vapaa (_testeri); _testi = NULL; Serial.println ("RAM:" + String (freeRam (), DEC)); Serial.println ("_ tester =" + Merkkijono ((uint16_t) _tester, DEC)); viive (1000); // pikakatselu}  

    En ole havainnut minkäänlaista RAM-muistin heikkenemistä tai kykyäni jakaa sitä dynaamisesti tällä menetelmällä, joten sanoisin, että se on käyttökelpoinen työkalu. FWIW.

    Testikoodisi on käyttötavan _2 mukainen. Määritä vain lyhytaikaiset puskurit - kuvasin edellisessä vastauksessani. Tämä on yksi harvoista käyttötavoista, joiden tiedetään olevan turvallisia.
    Toisin sanoen ongelmat tulevat esiin, kun aloitat prosessorin jakamisen muiden * tuntemattomien * koodien kanssa - juuri sitä ongelmaa luulet välttävän. Yleensä, jos haluat jotain, joka toimii aina tai epäonnistuu linkityksen aikana, teet kiinteän enimmäiskoon varauksen ja käytät sitä uudestaan ​​ja uudestaan, esimerkiksi pyytämällä käyttäjää välittämään sen sinulle alustuksessa. Muista, että käytät yleensä sirua, jossa ** kaiken ** on mahtuttava 2048 tavuun - ehkä enemmän joillakin taulukoilla, mutta myös ehkä paljon vähemmän muilla.
    @EdgarBonet Kyllä, tarkalleen. Halusin vain jakaa.
    @ChrisStratton Uskon, että vain tällä hetkellä tarvittavan kokoisen puskurin dynaaminen allokointi on muistirajoitusten takia ihanteellista. Jos puskuria ei tarvita säännöllisen käytön aikana, muisti on käytettävissä muulle. Mutta näen, mitä sanot: Varaa varattu tila, jotta jokin muu ei vie tilaa, jota tarvitset puskurille. Kaikki upea tieto ja ajatukset.
    Pelkästään tarvittavan kokoisen puskurin dynaaminen jakaminen on riskialtista, koska jos jotain muuta jaetaan ennen sen vapauttamista, sinulle voi jäädä pirstoutumista - muistia, jota et voi käyttää uudelleen. Dynaamisella allokoinnilla on myös seurantakulut. Kiinteä allokointi ei tarkoita sitä, ettet voi moninkertaistaa muistin käyttöä, se tarkoittaa vain sitä, että jakaminen on tehtävä ohjelman suunnittelussa. Puskurin, jolla on puhtaasti paikallinen laajuus, saatat myös punnita pinon käyttöä. Et ole myöskään tarkistanut mahdollisuutta malloc () epäonnistua.
    @ChrisStratton Oikea, tuo koodinpätkä ei tarkista, toimiiko malloc () vai ei. Sitten taas, että koodinpala käyttää tunnetusti rasvaisia ​​Arduino-kirjastoja ja -rutiineja. :) Kiitos oivalluksestasi. Erittäin kohtuullinen ja arvokas.
    "se voi olla vaarallista, jos et tiedä sen vivahteita, mutta se on hyödyllistä." melko paljon tiivistää kaiken kehityksen C / C ++: ssa. :-)
    #6
    +4
    Mikael Patel
    2017-10-19 15:55:16 UTC
    view on stackexchange narkive permalink

    Onko malloc (): n ja free (): n käyttö Arduinon kanssa todella huono idea?

    Lyhyt vastaus on kyllä. Seuraavassa on syyt:

    Kyse on MPU: n ymmärtämisestä ja siitä, miten ohjelmoida käytettävissä olevien resurssien rajoissa. Arduino Uno käyttää ATmega328p -MPU: ta, jossa on 32 kt ISP-flash-muistia, 1024B EEPROM ja 2KB SRAM. Se ei ole paljon muistiresursseja.

    Muista, että 2 kt: n SRAM-muistia käytetään kaikkiin globaaleihin muuttujiin, merkkijono-litaleihin, pinoon ja mahdolliseen kasan käyttöön. Pinossa on oltava myös tilaa ISR: lle.

    muistiasettelu on:

    SRAM map

    Nykypäivän tietokoneissa / kannettavissa tietokoneissa on enemmän kuin 1.000.000 kertaa muistin määrä. 1 Mt: n oletuspinotila säiettä kohti ei ole harvinaista, mutta täysin epärealistista MPU: lla.

    Upotetun ohjelmistoprojektin on tehtävä resurssibudjetti. Tässä arvioidaan ISR-viive, tarvittava muistitila, laskentateho, käskyjaksot jne. Valitettavasti ei ole ilmaisia ​​lounaita, ja kova reaaliaikainen upotettu ohjelmointi on vaikein ohjelmointitaidon hallita. / p>

    Aamen siihen: "[H] ARD reaaliaikainen sulautettu ohjelmointi on vaikeinta ohjelmointitaitoa hallita."
    Onko mallocin toteutusaika aina sama? Voin kuvitella, että malloc vie enemmän aikaa, kun se etsii lisää käytettävissä olevasta ramista sopivaa paikkaa? Tämä olisi vielä yksi argumentti (paitsi, että ram on loppumassa), jotta muistia ei jaeta tien päällä?
    @Paul Kasa-algoritmit (malloc ja vapaat) eivät yleensä ole vakiona suoritusaikaa, eivätkä ne palaa takaisin. Algoritmi sisältää haku- ja tietorakenteet, jotka edellyttävät lukituksia säikeiden käytössä (samanaikaisuus).
    #7
      0
    Kelly S. French
    2019-05-09 21:31:11 UTC
    view on stackexchange narkive permalink

    Okei, tiedän, että tämä on vanha kysymys, mutta mitä enemmän luen vastauksia läpi, sitä enemmän palaan jatkuvasti huomiota herättävälle havainnolle.

    Pysäytysongelma on todellinen

    Tässä näyttää olevan yhteys Turingin pysähtymisongelmaan. Dynaamisen allokoinnin salliminen lisää mainitun "pysähtymisen" todennäköisyyksiä, joten kysymyksestä tulee riski-suvaitsevaisuus. Vaikka on kätevää ilmoittaa mahdollisuudesta, että malloc () epäonnistuu ja niin edelleen, se on silti pätevä tulos. OP: n esittämä kysymys näyttää koskevan vain tekniikkaa, ja kyllä ​​käytettyjen kirjastojen yksityiskohdilla tai tietyllä MPU: lla on merkitystä; keskustelu kääntyy kohti ohjelman pysähtymisen tai muun epänormaalin loppun riskin vähentämistä. Meidän on tunnustettava ympäristöt, jotka sietävät riskejä huomattavasti eri tavalla. Harrastushankkeeni näyttää kauniit värit LED-nauhalla ei tapa joku, jos tapahtuu jotain epätavallista, mutta sydän-keuhkokoneen sisällä oleva MCU todennäköisesti.

    Hei herra Turing, nimeni on Hubris

    LED-nauhalleni en välitä, jos se lukittuu, nollaan sen. Jos olisin MCU: n ohjaamassa sydän-keuhkokoneessa, sen lukkiutumisen tai toimintahäiriön seuraukset ovat kirjaimellisesti elämää ja kuolemaa, joten kysymys malloc () ja free () tulisi jakaa siihen, miten aiottu ohjelma käsittelee mahdollisuutta osoittaa herra Turingin kuuluisa ongelma. Voi olla helppo unohtaa, että se on matemaattinen todiste, ja vakuuttaa itsellemme, että jos vain olemme riittävän älykkäitä, voimme välttää laskennan rajojen kärsimisen.

    Tähän kysymykseen on saatava kaksi hyväksyttyä vastausta, yksi niille, jotka joutuvat vilkkumaan tuijottaessaan pysähtymisongelmaa kasvoilleen, ja toinen kaikille. Vaikka useimmat arduinon käyttötavat eivät todennäköisesti ole tehtäväkriittisiä tai elämän ja kuoleman sovelluksia, ero on edelleen olemassa riippumatta siitä, mitä MPU: ta koodaat.

    En usko, että pysäytysongelma pätee tässä erityistilanteessa, kun otetaan huomioon, että kasan käyttö ei välttämättä ole mielivaltaista. Jos sitä käytetään hyvin määritellyllä tavalla, kasan käytöstä tulee ennustettavasti "turvallista". Pysähtymisongelman kohta selvitettiin, voidaanko määrittää, mitä tapahtuu välttämättä mielivaltaiselle ja ei niin hyvin määritellylle algoritmille. Se koskee todellakin paljon enemmän ohjelmointia laajemmassa mielessä, ja sellaisenaan mielestäni sillä ei ole erityistä merkitystä tässä. Mielestäni ei ole lainkaan merkitystä olla täysin rehellinen.
    Myönnän retorisen liioittelun, mutta asia on tosiasiassa, jos haluat taata käyttäytymisen, kasan käyttö merkitsee paljon suurempaa riskitasoa kuin kiinni vain pinon käytöstä.
    #8
    -3
    JSON
    2015-03-01 13:07:39 UTC
    view on stackexchange narkive permalink

    Ei, mutta niitä on käytettävä erittäin varovasti kohdennetun muistin vapauttamiseksi. En ole koskaan ymmärtänyt, miksi ihmisten mielestä suoraa muistinhallintaa tulisi välttää, koska se tarkoittaa epäpätevyyttä, joka ei yleensä ole yhteensopiva ohjelmistokehityksen kanssa.

    Sanotaan, että käytät arduinoa ohjaamaan dronea. Mikä tahansa virhe missä tahansa koodin osassa saattaa aiheuttaa sen putoamisen taivaalta ja satuttaa jotakuta tai jotain. Toisin sanoen, jos jollakin ei ole osaamista käyttää mallocia, hänen ei todennäköisesti pitäisi koodata ollenkaan, koska on niin monia muita alueita, joilla pienet virheet voivat aiheuttaa vakavia ongelmia.

    Onko mallocin aiheuttamia vikoja vaikeampaa jäljittää ja korjata? Kyllä, mutta kyse on pikemminkin koodaajien turhautumisesta kuin riskistä. Mitä tulee riskiin, mikä tahansa osa koodistasi voi olla yhtä tai enemmän riskialtista kuin malloc, jos et ryhdy toimiin varmistaaksesi, että se tehdään oikein.

    On mielenkiintoista, että käytit droonia esimerkkinä. Tämän artikkelin (http://mil-embedded.com/articles/justifiably-apis-militaryaerospace-embedded-code/) mukaan "riskin vuoksi dynaaminen muistin allokointi on kielletty DO-178B-standardin mukaisesti turvallisesti kriittinen upotettu avioniikkakoodi. "
    DARPA: lla on pitkä historia, jonka mukaan urakoitsijat voivat kehittää teknisiä tietoja, jotka sopivat heidän omalle alustalleen - miksi heidän ei pitäisi, kun veronmaksajat maksavat laskun. Siksi heille maksaa 10 miljardia dollaria kehittää mitä muut voivat tehdä 10000 dollaria. Melkein kuulostaa siltä, ​​että käyttäisit armeijan teollisuuskompleksia rehellisenä viitteenä.
    Dynaaminen allokointi näyttää kutsulta ohjelmallesi osoittamaan pysäytysongelmassa kuvatut laskentarajat. On joitain ympäristöjä, jotka pystyvät käsittelemään pienen määrän tällaisen pysähtymisen riskiä, ​​ja on olemassa ympäristöjä (avaruus, puolustus, lääketiede jne.), Jotka eivät siedä mitään hallittavaa riskiä, ​​joten ne kieltävät toiminnot, joita "ei pitäisi" epäonnistuu, koska 'sen pitäisi toimia' ei ole tarpeeksi hyvä, kun käynnistät rakettia tai ohjaat sydän- / keuhkokonetta.


    Tämä Q & A käännettiin automaattisesti englanniksi.Alkuperäinen sisältö on saatavilla stackexchange-palvelussa, jota kiitämme cc by-sa 3.0-lisenssistä, jolla sitä jaetaan.
    Loading...