Buongiorno a tutti, ho usato alcuni sketch che fanno uso della ricorsione(come gli esperti sapranno, sarebbe richiamare una funzione all'interno di se stessa) e non ho avuto problemi, ora ne sto creando uno che occupa circa il 45% di ram e 20% di flash su di un Mega 2560 ma dopo qualche giorno di bestemmie sono arrivato alla conclusione che il micro, sopratutto nei primi 20 secondi dopo l'avvio si resetta quando uso questo metodo.
Possibile che ci sia qualche problema di memoria? Mi sebra strano che sia la ram, ne ho a disposizione una buona quantita', cosa puo' causare il crash?
Grazie dell'attenzione
Ok, ci sono arrivato, ho seguito il percorso che fa il mio programma, mi sono accorto che non faceva altro che inserirsi all'interno della stessa pesante funzione, quindi saturavo la memoria, e probabilmente andavo in overflow, ho sistemato tutto con dei return, ho imparato che la ricorsione non e' adatta a micro...
essereumano:
ho imparato che la ricorsione non e' adatta a micro...
Questo non è vero, la ricorsione è una delle caratteristiche del C/C++, se usata nel modo corretto non c'è nessuno overflow, certo che se all'interno della ricorsione mandi un indice, o usi la memoria dinamica (questo si che è il male su i piccoli micro), oltre il limite massimo perché non fai verifiche è normale che il micro va in crash.
essereumano:
... ho imparato che la ricorsione non e' adatta a micro...
... non è esatto ... è che va valutato l'impatto sulla memoria SRAM ricordando che quella di una piccola MCU come quella di Arduino è piuttosto ridotta e quindi, chiama e richiama è facile trovarsi con uno stack-overflow.
Guglielmo
@Astro:
... solito sincronismo eh ![]()
Si, ovviamente mi riferivo alla poca sram disponibile che permette di giocare limitatamente con queste tecniche. ![]()
la ricorsione non è proprio adatta al C/C++ per come sono stati concepiti.
la ricorsione è la kryptonite delle prestazioni!
Se la si vuole usare bisogna scegliere un'altro tipo di linguaggio e non un'altro tipo di CPU.
vbextreme:
la ricorsione non è proprio adatta al C/C++ per come sono stati concepiti.
Non diciamo cavolate, una delle caratteristiche principali del C è proprio la ricorsione, utilissima in ambito matematico e scientifico, che poi vada usata accendendo il cervello questo è un altro paio di maniche.
Non c'è nessun problema ad usare la ricorsione sulle mcu, se fatto nel modo corretto e previa verifica di come si comporta il compilatore in questi casi, non tutti i compilatori per mcu supportano questa funzionalità o la supportano con dei limiti.
La ricorsione non va mai usata a prescindere da mcu/CPU che si stia usando.
Il C permette tante cose, ma questo non significa che bisogna adoperarle tutte.
In primis la ricorsione, che è inutile perché sempre sostituibile da metodi lineari molto più performanti e molto meno avari di risorse, non esiste nessuna sorta di motivo, se non l'eccezione che conferma la regola, di usare un metodo ricorsivo.
Se presentassi un codice ricorsivo al mio amico professore sarei fustigato in aula magna
vbextreme:
Se presentassi un codice ricorsivo al mio amico professore sarei fustigato in aula magna
Se lo fai in un contesto sbagliato/inutile, oppure applicandolo in modo errato, avrebbe tutte le ragioni per farlo, se lo fai quando realmente serve e nel corretto modo non ha alcun motivo per farlo. ![]()
Che nella vita "normale" del programmatore la ricorsione sia inutile sono d'accordo con te, che nel mondo dei calcoli matematici e scientifici sia utilissima è fuori di ogni discussione.
Da qui affermare che non si deve usare su una mcu ci sono in mezzo 50000 sfumature di codice ![]()
La ricorsione semplifica certi algoritmi e per taluni linguaggi, spesso usati dai matematici, è anche l'unica via.
Ma nel C, in generale e ancor piu sulle mcu, nel 99% dei casi è solo uno spreco di risorse.
Lo stesso compilatore non ha armi per ottimizzare le funzioni ricorsive.
Quindi è sempre meglio evitare, così facendo si avrà codice molto più performante anche di 100 volte rispetto al metodo ricorsivo.
Come esercizio per i più smaliziati consiglio l'implementazione della famosa sequenza di Fibonacci, sia con il metodo ricorsivo, sia senza.Visualizzando il numero massimo ottenibile con entrambi i metodi e il tempo di esecuzione.
Io non so sinceramente se ridere o piangere leggendo alcune risposte...
Non voglio fare il saputello di turno, ma purtroppo devo difendere alcune cose che purtroppo sono state dette in modo errato.
Innanzitutto ci sono dei problemi che non possono essere risolti con la ricorsione e ci sono altri problemi che sono risolvibili solo tramite la ricorsione.
Non riportate esempi come il fattoriale o Fibonacci che sono implementate da algoritmi iterativi... Pensate ad algoritmi più complessi come la Torre di Hanoi, il problema delle 8 regine, la risoluzione di un labirinto o dijkstra e altro. Tutte queste problematiche sono facilmente risolvibili tramite ricorsione e quasi impossibili o davvero lunghe da scrivere tramite iterazioni..
@DomenicoZarcone: ... ecco, invece di ridere o piangere, comincia con il rispettare il regolamento ![]()
Quindi, cortesemente, presentati QUI (spiegando bene quali conoscenze hai di elettronica e di programmazione ... possibilmente evitando di scrivere solo una riga di saluto) e leggi con attenzione il suddetto REGOLAMENTO ... Grazie.
Guglielmo
Un buon esempio di ricorsione e' quando ti mettono in tavola un piatto di ciliege, e tu inizi ricorsivamente a spazzolarle via una dopo l'altra, finche' prima ancora di rendertene conto, il piatto e' vuoto (e gli altri che sono rimasti senza ti guardano tutti male
:D)
Scherzi a parte, ma una funzione che richiama se stessa dall'interno di se stessa, non finisce sempre per andare in overflow o inchiodare tutto ? ... come lo svuota lo stack se non esce mai ?
Etemenanki:
Scherzi a parte, ma una funzione che richiama se stessa dall'interno di se stessa, non finisce sempre per andare in overflow o inchiodare tutto ? ... come lo svuota lo stack se non esce mai ?
Ovvio che c'è una condizione per cui inizia a fare a ritroso "return" ![]()
Classico è appunto l'esempio del fattoriale (... il primo che ho trovato, NON specifico per Arduino) ...
/* Fattoriale di un numero */
#include <stdio.h>
int calcFatt(int numero);
main(){
int n,fat;
printf("\nCalcola il fattoriale di un numero");
printf("\n\nIntrodurre il numero ");
scanf("%d",&n);
fat=calcFatt(n);
printf("\n Fattoriale di %d = %d",n,fat);
}
/* Calcola Fattoriale utilizzando la Ricorsione*/
int calcFatt(int numero){
int f;
if (!numero)
f=1;
else
f=numero*calcFatt(numero-1);
return f;
}
Guglielmo
Scherzi a parte, ma una funzione che richiama se stessa dall'interno di se stessa, non finisce sempre per andare in overflow o inchiodare tutto ? ... come lo svuota lo stack se non esce mai ?
Si in teoria se per ogni chiamata si spingono dati nello stack si finisce per superare la profondità dello stack sconfinando. Tuttavia se il valore ritornato viene impiegato nel calcolo essendo questo già computato viene meno la necessità di mantenerlo nello stack e la posizione può essere riciclata. Ma se ciò sia o meno possibile richiede l'analisi di ciò che ha tradotto il compilatore e sinceramente l'asm non mi affascina più di tanto. ![]()
DomenicoZarcone:
Io non so sinceramente se ridere o piangere leggendo alcune risposte...
Non voglio fare il saputello di turno, ma purtroppo devo difendere alcune cose che purtroppo sono state dette in modo errato.Innanzitutto ci sono dei problemi che non possono essere risolti con la ricorsione e ci sono altri problemi che sono risolvibili solo tramite la ricorsione.
Non riportate esempi come il fattoriale o Fibonacci che sono implementate da algoritmi iterativi... Pensate ad algoritmi più complessi come la Torre di Hanoi, il problema delle 8 regine, la risoluzione di un labirinto o dijkstra e altro. Tutte queste problematiche sono facilmente risolvibili tramite ricorsione e quasi impossibili o davvero lunghe da scrivere tramite iterazioni..
Tutti, e dico tutti, gli algoritmi ricorsivi si possono trasformare in lineari.
La ricorsione è da evitare con il linguaggio C per come è stato strutturato.
prendendiamo in esame la suddetta mcu avr.
Per una chimata semplice di una funzione il compilatore deve salvare il PC(16bit), Y(16bit), poi adesso può otimmizare oppure no, salvando altri 16 registri nello stack. Abbiamo circa 20 registri nello stack che sono 20 byte per chiamata.
In base al tipo di ricorsione il compilatore potrebbe necessitare il salvataggio dei registri usati generalmente per il passaggio degli argomenti.
una funzione ricorsiva quale
int aaa(int32_t a, int* z)
richiede il savataggio di ulteriori 6 registri.
con +26 byte di memoria solo per salvare lo stack diventa impensabile usare un algoritmo ricorsivo, in questo caso la MCU spreca 52 cicli di clock per niente e 26 byte di memoria solo per una chiamata ad un funzione ricorsiva che fa poco o niente.
Stesso discorso per le CPU, ancora peggio per CPU quali vecchi x86 32bit dove i registri sono pochi e una funzione ricorsiva si tramuta in un continuo salvataggio e ripristino dello stack.
Esistono altri linguaggi che si basano sulla ricorsione e funzionano piu velocemente del C in tale ambito.
Quindi, io consiglio di studiare di piu.
dimenticavo,
semplice soluzione hanoi lineare
8 queens
etc, etc, etc.
vbextreme:
Tutti, e dico tutti, gli algoritmi ricorsivi si possono trasformare in lineari.
Su questo non ci sono dubbi, per quanto riguarda lo stack il discorso è un pochino più complesso di come lo presenti, quello che dici è giusto ma non sempre valido, dipende molto da come è realizzato il compilatore.
Non ho mai verificato come si comporta in questo caso gcc però l'ho fatto per il CBuilder (Borland) su PC e per MDK Arm di Keil che uso per i processori ARM, tutti e due non aggiungono posizioni allo stack oltre alla prima chiamata della funzione, gestiscono localmente le chiamate senza dover passare per lo stack ad ogni iterazione.
In pratica a seconda del compilatore puoi avere sia la situazione che ad ogni iterazione vengono aggiunti dati allo stack, con relativo aggravio di cicli macchina e uso stack, sia che viene utilizzato un singolo salvataggio nello stack e un numero fisso di byte aggiuntivi, in ram, per gestire localmente le iterazioni.
In tutti i casi gcc X86, da verificare per AVR, dispone della Tail Call Optimization che permette di ridurre notevolmente l'uso dello stack durante la ricorsività.
Ripeto, per capire l'enorme limitazione della ricorsione con il linguaggio C basta fare un semplice esercizio.
Fate l'algoritmo di Fibonacci, misurare il tempo impiegato con il metodo ricorsivo e con quello iterativo. L'esempio di Fibonacci è ottimo per imparare la ricorsione, un algoritmo molto semplice ma può far capire molte cose.
Prima di eseguire l'algoritmo ricorsivo calcolare a mano quale numero massimo sarà rappresentabile, fate la medesima cosa con l'iterativo.
Postate qui, codice usato e i risultati