Newsgroup: it.hobby.elettronica.digitale
Titolo: Allocazione dinamica della memoria

Ciao,
 
in un'applicazione (basata sul C18 Microchip) sto "giocando" con malloc 
e heap in maniera abbastanza pesante.
 
Come posso essere sicuro di non fare cavolate nel firmware? Senza 
aspettare un reset del micro intendo :) Anche perché potrebbe non 
andarmi così bene!
 
Ci sono dei modi, verifiche o altro che posso inserire affinché sia 
certo di aver allocato correttamente la memoria necessaria?
 
Grazie!
Marco


ne so meno di te,tanto per confonderti di piů ma chiarire a me
dovrebbe essere possibile con  direttive mettere in uno  spazio RAM ben 
preciso le variabili di dimensione nota
E far si che l'heap eventualmente da allocare cresca in direzione opposta?
 
Poi se una volta allocato ci scrivi dovunque un byte di comodo es:0xAA col 
debugger potrebbe essere utile verificare a colpo d'occhio dove sei 
arrivato,oppure controllare periodicamente se un byte in una posizione 
assoluta che non dovrebbe essere invaso dalla variabilona allocata.
 
non sto parlando da pari a pari,propongo delle cose per capire se siano 
strampalate
 
buona settimana a tutti 
 
 


wrote:

No. Servirebbe una MMU o qualcosa di simile, ma nei micro a 8 bit non
ho ancora mai viste.
Una protezione della memoria via software non sarebbe efficace per la
natura stessa del C.
L'unica soluzione e' scrivere bene il programma mettendo controlli
molto stretti quando si ha il dubbio che un puntatore possa andare
fuori dal suo range.
Se non sbaglio, nei PIC la memoria dati dove risiede l'heap e'
fisicamente separata dallo stack quindi non hai nemmeno il pericolo
che uno invada l'altro.

 che uno invada l'altro.
 
 
Ok, l'unico problema che vedo � riuscire a risalire con esattezza a 
quanto hanno bisogno le librerie di terze parti. Una volta capito questo 
aggiungo il "caso peggiore" con il mio codice e dovrei avere la 
dimensione massima dell'heap da allocare.
 
Ciao
Marco
 
 

 dimensione massima dell'heap da allocare.
 
Ok, pensavo che il problema principale fosse la corruzione dello heap,
puntatori vaganti e simili.
In generale nei sistemi embedded, soprattutto con poca ram come i
micro, lo heap e' una grossa fonte di rogne misteriose perche'
normalmente e' impossibile prevedere se malloc avra' successo. Pensa
al caso di allocare un grosso array di char per un buffer in un heap
fortemente frammentato. In teoria se vuoi assicurare che malloc
funzioni anche per caso peggiore, devi prevedere un heap enorme. Tanto
vale a quel punto allocare staticamente le variabili.

 vale a quel punto allocare staticamente le variabili.
 
 
Ho capito. In effetti il tuo ragionamento � molto sensato.
Ero partito con la strada del malloc per rendere molto pi� flessibile 
alcune cose ed evitare la ricompilazione: in pratica all'avvio andavo a 
leggere da qualche parte le impostazioni e allocavo di conseguenza i buffer.
 
Ma possiamo anche fare un ragionamento diverso: visto che *comunque* 
devo definire una dimensione massima possibile, nulla mi vieta di 
allocare staticamente sempre questa dimensione. Poi quando leggo le 
impostazioni semplicemente uso solo quella che mi serve.
 
Direi che mi risparmio parecchie rogne :)
Marco
 
 

 impostazioni semplicemente uso solo quella che mi serve.
 
 
Così però è troppo rigido.
Vediamo se troviamo una via di mezzo. Al momento sono in queste condizioni:
 
typedef struct {
	/* altri campi */
	UINT16 buffer_size;
	INT16 *buffer;
} sample_t;
 
ho poi un file .h con alcune definizioni:
 
#define SAMPLES		3
#define SMP_1_SIZE	100
#define SMP_2_SIZE	200
#define SMP_3_SIZE	150
 
sample_t samples[SAMPLES] = {
	{ [...], SMP_1_SIZE },  // tralascio gli altri campi
	{ [...], SMP_2_SIZE },  // che non ci interessano
	{ [...], SMP_3_SIZE }
};
 
 
all'avvio del programma vado a fare:
 
for (i = 0; i < SAMPLES; i++)
   samples[i].buffer = malloc(sizeof(INT16) * samples[i].buffer_size);
 
 
Non mi serve modificare lo spazio una volta allocato. Mi basterebbe 
poterlo fare all'avvio del programma.
 
 
Per evitare l'allocazione dinamica mi viene in mente una cosa del genere:
 
static INT16 megabuffer[1000];
 
INT16 *buf_ptr(UINT8 sample) {
   UINT16 p = 0;
   for (UINT8 i = 0; i < sample; i++)
     p += samples[i].buffer_size;
 
   return &megabuffer[p];
}
 
 
l'utilizzo sarebbe:
 
INT16 *p;
 
p = buf_ptr(1); // accedo al secondo sample
p[3] = 0;	// accedo al quarto valore dell'array
 
 
In realtà riempirei una tabella con i puntatori corretti, così da 
accedere direttamente anziché utilizzare la funzione buf_ptr(). Era solo 
per chiarire il concetto.
 
 
Che ne dici?
Marco
 

 Direi che mi risparmio parecchie rogne :)
 
Esatto, le applicazioni embedded non sono calcolatori per uso universale.
Nella maggior parte dei casi devono cioè implementare una serie di 
features ben specifiche: non sono macchine di uso generico in cui se 
avanzano risorse è meglio perché posso fare più cose. In questi casi 
quindi non ha senso risparmiare a prescindere sulla memoria occupata, 
perché è ottimizzazione preventiva.
 
Un'applicazione che usa staticamente *tutta* la RAM a disposizione e 
svolge tutte le funzioni richieste è altrettanto (o forse anche più) 
valida di una che usa memoria dinamica, lasciando magari ogni tanto un 
po' di memoria libera in qua e in là.
 
Io cerco sempre di non usare memoria dinamica nelle applicazioni 
embedded che sviluppo. La memoria dinamica la lascio per compiti 
"dinamici" di natura, es: la scheda può ricevere messaggi di diverso 
tipo e lunghezza, allora ha senso avere uno heap, specifico per questo 
caso, che alloca un buffer per la lunghezza esatta del messaggio che sto 
ricevendo.
 
In un caso come questo non ci sono rischi, basta che lo heap sia grande 
quanto il messaggio più grande che posso ricevere. E in ogni caso le 
condizioni di errore sono facilmente gestibili.
 
Se inizi a usare la memoria dinamica per tutto, l'applicazione comincia 
a diventare meno deterministica e ti assumi rischi importanti: non 
determinismo del tempo di allocazione, possibilità di fallimento 
dell'allocazione, frammentazione, etc...
 
Soprattutto la frammentazione è un problema importante che può passare 
inosservato ai primi test. Magari va tutto bene però dopo una settimana 
che la tua scheda è accesa un'allocazione fallisce.
 
Ciao!
 

 ha scritto nel messaggio 
news:kasmi0$lpc$1...@speranza.aioe.org...

molto chiaro,grazie anche da parte mia
Buon Natale 
 
 


I due casi direi che sono equivalenti, forse preferirei il primo. Non 
vedo particolari rischi nell'uso della memoria dinamica in questo frangente.
 
In realtà a te andrebbe bene qualcosa di statico e la stai usando per 
ovviare a limitazioni del linguaggio.
 
Se usi gcc, dai un'occhiata a questo, è proprio quello che ti serve
http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
 
 
Un alternativa alle soluzioni precedenti è usare delle macro per 
automatizzare le cose. C'è da lavorarci un po' ma arriveresti ad una 
soluzione completamente statica ma più facilmente manutenibile a compile 
time. Se ti interessa posso approfondire questa parte, ma è richiesto un 
po' di tempo
 
 
Ciao!
 

 po' di tempo
 
 
Stavo già lavorando a qualche macro, prima di provare la strada con area 
statica comune e puntatori ai vari blocchi.
 
Più che altro potrebbe essermi utile avere il tuo parere sulla strada 
che seguiresti, poi provo da solo a implementarla.
 
Come hai capito l'idea è di scrivere delle funzioni per gestire aree di 
memoria dove allocare strutture dati che siano configurabili facilmente, 
a run-time, oppure anche a compile-time tramite un semplice header dove 
specifico le dimensioni degli array (delle strutture dati) che mi servono.
 
Grazie mille!
Marco
 

 che la tua scheda è accesa un'allocazione fallisce.
 
 
Si, ho esperienza, non diretta per fortuna!
Marco
 
 

 limiti a caratteri ascii più comodo - li tratti come stringhe.
 
Direi che il mio approccio coincide con il tuo :)
Qualunque tipo di allocatore, in questo specifico caso, va bene. Anche 
il megabuffer che hai usato nell'esempio dell'altro thread è ottimo direi.
 

 il megabuffer che hai usato nell'esempio dell'altro thread è ottimo direi.
 
 
Ok, grazie :)
 
Marco
 
 

Il 20/12/2012 11:57, Francesco Sacchi ha scritto:

Mi inserisco qui nella vostra discussione, perchè non capisco una cosa. 
Perché penare nell'implementare un allocatore statico, a partire da un 
buffer largo abbastanza, quando posso semplicemente definire le 
variabili che mi servono nel sorgente.  Sarà compito del linker trovare 
spazio in RAM per queste variabili e, non trovandolo, ad avvisarmi.
 
Chiaramente sono d'accordo nel rifuggere l'allocazione dinamica che 
nelle applicazioni embedded può solo fare danni (tra l'altro, mi sembra 
che lo standard MISRA-C vieta giustamente il suo utilizzo).
 

 condizioni di errore sono facilmente gestibili.
 
Questa è una osservazione interessante che vale la pena approfondire.
 
Se l'applicazione embedded può ricevere messaggi di varia natura e 
lunghezza con una frequenza non nota (cosa che comunemente succede), 
quanto spazio allocare staticamente per il buffer di ricezione? 
Allocando la massima dimensione di un singolo messaggio mi impedisce di 
poter ricevere il secondo messaggio mentre il primo è ancora in fase di 
elaborazione.
 
Alla fine, quello che faccio, è aumentare la dimensione del buffer dei 
messaggi di ingresso al massimo possibile, utilizzando tutta la RAM a 
disposizione (come hai detto tu, non è un problema se la memoria è usata 
completamente), così da poter gestire la maggior parte dei messaggi 
possibili.
 
Chiaramente questa non è una operazione immediata in quanto va fatta a 
tentativi: allarga e compila, allarga ancora e compila fin quando il 
linker ti dà picche.  E naturalmente l'approccio è anche molto scomodo 
quando bisogna aggiungere variabili all'applicazione: in questo caso il 
buffer va ridotto di conseguenza.
 

 spazio in RAM per queste variabili e, non trovandolo, ad avvisarmi.
 
 
Nel mio caso cercavo un modo per rendere facilmente riutilizzabile il 
codice, potendo specificare una dimensione diversa per ciascuna area 
dati di ogni elemento del mio array.
 
Non ho capito come faresti tu.
 
Marco
 
 

 possibili.
 
 
Io invece la vedo diversamente.
Supponiamo una linea seriale per semplicità.
Solitamente implemento un buffer circolare di ricezione da cui attingo 
nel main loop i caratteri e li metto nel buffer dati (liberando 
carattere dopo carattere il buffer seriale).
 
Quando il messaggio è terminato avrò il buffer seriale vuoto e quello 
dati che contiene il nuovo messaggio che andrò subito a elaborare.
 
Terminata l'elaborazione del messaggio torno a svuotare il buffer seriale.
 
Quindi la dimensione del buffer dati corrisponderà alla massima 
lunghezza dei messaggi in arrivo. Mentre la dimensione del buffer 
seriale sarà tale da garantirmi il tempo necessario a processare il 
messaggio considerata la velocità di trasmissione.
 
Marco
 


Non ho capito neanche io.  Semplicemente io definisco le variabili in C:
 
struct {
   unsigned int id;
   char nome[32];
   char indirizzo[32];
} clienti[MAXNUM_CLIENTI];
 
Si tratta di un'allocazione statica (fatta a livello di modulo .c e non 
all'interno di funzioni) che viene gestita direttamente dal linker.
 

 } clienti[MAXNUM_CLIENTI];
 
 
Quello che serve a me č poter definire per ogni elemento "clienti", una 
diversa lunghezza dei campi "nome" e "indirizzo".
 
Spero di essermi spiegato ora :)
Ciao!
 
Marco
 
 

 Quello che serve a me č poter definire per ogni elemento "clienti"
 
 
Leggasi: per ogni elemento dell'array "clienti"
 
 
 

 Spero di essermi spiegato ora :)
 
Forse sono duro di comprendonio, scusami.
 
L'obiettivo � quello di minimizzare l'occupazione della memoria 
considerando che hai pochi clienti con nomi molto lunghi e molti clienti 
con nomi molto corti?
 
Siccome stiamo parlando sempre di allocazione statica, allora:
 
char nome1[] = "Cip";
char nome2[] = "Michelangelo Buonarroti";
char nome3[] = "Ciop";
 
struct {
   unsigned int id;
   char *nome;
   char *indirizzo;
} clienti[MAXNUM_CLIENTI] = {
   { 1, nome1, indirizzo1 },
   { 2, nome2, indirizzo2 },
   { 3, nome3, indirizzo3 },
};
 
Forse sto semplificando troppo?  Potresti fare un esempio concreto?
 

 messaggio considerata la velocit� di trasmissione.
 
Immagina dei messaggi di 50 byte (e quindi un buffer di 50 byte) a 
38400bps: 13ms.  Se hai un task che pu� durare 20ms (per esempio, 
rinfresco di un display), rischi di ricevere un secondo messaggio quando 
il primo non � stato ancora processato.  Avresti una condizione di FIFO 
FULL e la perdita di un messaggio.
 
Dovresti calcolare il massimo tempo necessario per elaborare un 
messaggio sommando il tempo di esecuzione di ogni task (cosa non 
semplice, perch� dipende dal contesto).
 
 

 Forse sto semplificando troppo?  Potresti fare un esempio concreto?
 
 
Stiamo parlando di dati da acquisire. Quindi il contenuto non � noto a 
priori.
L'esempio l'avevo fatto qualche post fa, lo riporto con qualche 
informazione in pi�:
 
 
typedef struct {	// definizione del "canale" di acquisizione
     BYTE channel;
     BYTE state;
     // ecc... esempi di dati di servizio
     // qui sotto l'area dati
     UINT16 buffer_size;
     data_t *buffer;
} sample_t;
 
ho poi un file .h con alcune definizioni:
 
#define SAMPLES       3
#define SMP_1_SIZE    100
#define SMP_2_SIZE    200
#define SMP_3_SIZE    150
 
Quindi quando andr� a dichiarare:
 
sample_t samples[SAMPLES];
 
vorrei quindi ottenere che lo spazio allocato per la variabile data sia 
di 100 elementi per samples[0], 200 per samples[1] e 150 per samples[2].
 
Il punto � che a seconda delle misure che devo fare potrei dover 
cambiare la dimensione dell'area dati. Ad esempio potrei avere un unico 
canale da 1000 elementi o 10 canali da 50 elementi.
 
Dalla discussione con Francesco � emerso che potrei utilizzare 
l'allocazione dinamica all'avvio del micro oppure alloco staticamente 
l'area massima e poi la suddivido tramite puntatori come mi serve.
 
Ciao
Marco
 

 semplice, perch� dipende dal contesto).
 
 
Tu ora parli per� di un s.o., io mi riferivo a un'applicazione standard. 
Comunque non cambia il succo: dovresti gestire correttamente le priorit� 
(il display lo aggiorni quando hai tempo, mentre il buffer seriale 
dovrebbe avere una priorit� maggiore).
 
Certo pu� essere difficile calcolare esattamente il tempo necessario 
visto che come dici dipende dal contesto... ma a noi serve solo stimare 
il caso peggiore e aggiungere un po' di margine.
 
Spesso non serve nemmeno calcolarlo: in fase di sviluppo puoi 
intercettare il riempimento della FIFO e dimensionare di conseguenza il 
buffer. Oppure ancora meglio, metti in una coda i messaggi ricevuti non 
ancora elaborati, poi tieni monitorata la lunghezza della coda anche per 
lunghi periodi.
 
Marco
 

 l'area massima e poi la suddivido tramite puntatori come mi serve.
 
S� ho letto, ma non noto grandi vantaggi rispetto ad una "classica" 
definizioni di variabili globali.  Anzi, nel tuo caso hai del codice in 
pi� e pure la gestione della dimensione complessiva dell'heap (che devi 
calcolare come la somma dei vari buffer che ti servono).
 

 calcolare come la somma dei vari buffer che ti servono).
 
 
Per� posso appunto cambiare al volo la configurazione. E' chiaro che 
dipende da quello che ci devo fare, non c'� un'unica soluzione "migliore".
 
Ciao e grazie!
Marco
 
 

 dipende da quello che ci devo fare, non c'� un'unica soluzione "migliore".
 
Fammi capire, tu in fase di compilazione non conosci le dimensioni degli 
array, per� le conosci nelle prime fasi dell'avvio dell'applicazione. 
Come fai a conoscerle?  Se sono scritte nel codice (magari da qualche 
altra parte) devi comunque ricompilare.
 
 

 condizioni di errore sono facilmente gestibili.
 
Volevi dire che utilizzi uno heap per il buffer che contiene il 
messaggio da elaborare oppure per la FIFO della seriale?
Io pensavo ti riferissi alla FIFO che non pu� essere solo grande quanto 
il pi� grande messaggio che puoi ricevere.
 
Ma perch� usare una allocazione dinamica per copiare il messaggio 
ricevuto?  Lo heap deve essere dimensionato sul massimo, allora tanto 
vale allocare un array di dimensione massima e copiare l� (senza 
allocazioni dinamiche) il messaggio.
 
   unsigned char rx_msg[MSG_MAXLEN];
   size_t rx_msg_len = 0;
 
   void msg_process(void) {
     int complete = 0;
     while(!FIFORX_ISEMPTY()) {
       rx_msg[rx_msg_len++] = FIFORX_POP();
       if (msg_iscomplete(rx_msg, rx_msg_len)) {
         complete = 1;
         break;
       }
       if (rx_msg_len >= MSG_MAXLEN) {
         /* Gestione dell'errore di messaggio troppo lungo */
         return;
       }
     }
     if (!complete) return;
     /* Processa il messaggio */
     ...
     rx_msg_len = 0;  /* Pronto per l'altro messaggio */
   }
 

 altra parte) devi comunque ricompilare.
 
 
L'ho scritto in qualche post: potrebbero venire lette in eeprom, e 
aggiornate via seriale. In quel caso si riavvia il sistema e quindi 
l'allocazione avviene solo all'avvio.
 
Marco
 
 

 allocazioni dinamiche) il messaggio.
 
 
Infatti � quello che ho scritto anche io in risposta. La sua conclusione 
la trovi nel messaggio delle 11:57 del 20/12:
 
"Direi che il mio approccio coincide con il tuo
Qualunque tipo di allocatore, in questo specifico caso, va bene. Anche 
il megabuffer che hai usato nell'esempio dell'altro thread � ottimo direi. "
 
 
Ciao!
Marco
 
 


Capito, ora � chiaro.  Mi era sfuggito questo dettaglio.
 

 vedo difficile essere dinamica.
 
Infatti.


 possa scrivere nella video ram.
 
 
Basta un flag, oppure metti un doppio buffer e passa la paura :)
 
Ciao!
Marco