Ottimizzare la gestione delle stringhe

Sto realizzando una sorta di terminale o forse è meglio dire un pannello di controllo con la classica configurazione:
Arduino Uno + LCD 20x4 + keypad 4x4 + lettore SD.
Tale pannello lo uso per monitorare/comandare una seconda unità remota dedicata alla domotica (tutto fatto in casa).
Arrivo alla domanda e/o consiglio.
Arduino riceve dall'unita' remota via seriale una serie di comandi composti da 4 byte
Esempio: "#RST" - "#CNP" - "#CNC" ecc.
Per interpretare in modo veloce il tipo di comando avevo pensato a questa soluzione:

  string ListaComandi = "#RST#CNP#CNC#SET"
  string carRx = "#CNP"          // stringa in arrivo dalla seriale

  int Posizione = ListaComandi.indexOf(carRx);

  switch (Posizione) {
    case 0:
           // comando #RST
      break;
    case 4:
           // comando #CNP
      break;
  }

E' possibile ottimizzare tale codice?

Certo che è possibile, cambiandolo proprio ... :grin: :grin: :grin:

Premesso che hai sbagliato a scrivere, visto che il C è sensibile alle maiuscole ed alle minuscole .... la classe che vuoi usare tu è "String" e non "string" (... che, in realtà è un char array) ... io comunque sconsiglio sempre l'uso di detta classe su una MCU piccola come quella di Arduino che ha solo 2 KBytes di SRAM.

Ricorda che le String vengono allocate dinamicamente in memoria usando la malloc() e, ogni volta che ne cambi la loro lunghezza, vengono distrutte e riallocate !
Dato che non c'è un "garbage-collector" ... in breve tempo ti ritrovi con la memoria frammentata ed inutilizzabile, con conseguente blocco/perdita di controllo del programma.

La strada che io consiglio e invece quela di usare le stringhe del C, ovvero i char array null terminated (... ovvero, per indicare la fine di una stringa, si mette un byte a 0x00) e le funzioni della AVR libc per manipolarle, in particolare, quelle presenti in <string.h>.

Se guardi bene ce ne sono per tutti i gusti, comprese quelle per cercare un certo insieme di caratteri all'intero di una stringa e tornare l'indice di dove si è trovato ... strstr() :wink:

Gli strumenti per ottimizzare ora li hai ... vedi un po' come utilizzarli ...

Guglielmo

P.S. : Quelle funzioni sono altamente ottimizzate. Ho provato a sostituirle scrivendole io con cicli di for ... ma ... con pessimi risultati :grin: :grin: :grin:

@overmike, aggiungo: si l'algoritmo che hai pensato è ottimo, lo uso spesso. Soprattutto in VB e linguaggi ad alto livello.
Ma, come ti dice @Guglielmo, gli oggetti String sono pesanti. Puoi applicare lo stesso algoritmo ma con l'uso di stringhe (vettori di caratteri terminati da null ovvero carattere '\0') usando i vari comandi strncmp() oppure strstr()

Poi se Posizione lo dividi per 4 (nel tuo caso, la lunghezza del singolo comando) ottieni un valore progressivo 0,1,2.. invece di 0,4,8... meno semplice da gestire.
Puoi anche rendere più leggibile il codice usando delle costanti o degli enum:

#define K_CMD_RST 0
#define K_CMD_CNP 1
...
if(Posizione>=0) Posizione=Posizione/4;
switch (Posizione) 
{ case K_CMD_RST:
....
  case K_CMD_CNP:
...

Ok!
Mi si è aperto un nuovo mondo e io che pensavo di aver già imparato qualcosa.
Adesso e' tutto da rifare, almeno nell'approccio al "C"!
Vedrò di studiarci sopra.

Grazie di tutto!

Renzo

Ahahahah ... no, dai, non è tutto da rifare ... devi rivedere alcune cose, il concetto di base che volevi seguire resta :slight_smile:

Guglielmo

Arrivo da una discreta esperienza di Assembler e dopo vari passaggi sono arrivato a VB6, ma rimanendo affezionato al primo.
Con l'assembler puoi far di tutto, ma devi anche preoccuparti di tutto, con VB invece avevi un strada ben definita, ma non dovevi preoccuparti di niente (o quasi).

Qui con Arduino mi sembra di trovarmi a metà strada.
Quando affermi (e ti credo) "in breve tempo ti ritrovi con la memoria frammentata ed inutilizzabile...."
è qualcosa che non digerisco da un sistema che dovrebbe in certo qual modo guidarmi o bloccare certe scelte!

Va be'', scusa dello sfogo vedro' di darmi da fare

Renzo

overmike:
Arrivo da una discreta esperienza di Assembler...

Scusami, ma non sono d'accordo. Anzi provieni dall'assembly. Arduino si programma in C/C++ e il C è stato sviluppato proprio per sostituire l'assembly, perciò rimane un linguaggio orientato più verso il basso livello.
Inoltre stai lavorando su un microcontrollore (MCU), pensato come attuatore di circuiti elettronici, non ad un microprocessore su PC con sistema operativo e montagna di Ram.
Secondo me il VB6 ti ha "viziato" :wink:
Almeno io la vedo così. :smiley:

overmike:
...
Quando affermi (e ti credo) "in breve tempo ti ritrovi con la memoria frammentata ed inutilizzabile...."
è qualcosa che non digerisco da un sistema che dovrebbe in certo qual modo guidarmi o bloccare certe scelte!

Dimentichi che stai su una MCU (... non su un computer) e non c'è il sistema operativo ...
... il sistema operativo sei tu ... e sei tu che decidi cosa fare e quali rischi correre.

Il C ti mette a disposizione tutto (... quasi come l'assembler) e come funzionano malloc(), dealloc() e realloc() è piuttosto chiaro.
Chiunque le usi (... sia esplicitamente, sia implicitamente usando una classe che ne fa uso) ... deve sapere a cosa va incontro ... non c'è nessuno al di sopra a controllare XD

Guglielmo

Confesso che sono alla mia prima esperienza in assoluto con il "C" con le ovvie conseguenze del caso.

Cio' nonostante mi sento autorizzato a criticare il fatto che se un sistema di sviluppo e/o un linguaggio mette a disposizione una serie di funzionalità, risulti ragionevole pensare di poterle utilizzare senza alcuna riserva. Magari faranno perdere in efficienza la CPU, ma lontano dai miei pensieri sospettare che il loro uso possa compromettere la stabilità del sistema.

Mi sono riletto la documentazione delle varie funzioni stringa, e in nessun caso è stato riportato "attenzione all'uso delle funzioni stringa perche' puo creare instabilita".

Non dimenticate che sono stati proprio i vs consigli, che reputo comuque affidabili, a farmi cambiare rotta sullo sviluppo del mio software.

Renzo

p.s.
A tutt'oggi, lavoro ancora in assembler con un mitico Z80 con 32K di ram, mentre da poco ho abbandonato le MCU della serie ST6 che mettevano a disposizione 3000 byte per il codice e poco meno di 128 byte di RAM, e posso assicurarvi che gli ho fatto fare miracoli.

Mah ... vedi il problema è che Arduino è per la "diffusione alla massa" ... e alla "massa" non puoi fargli sbattere la faccia su cose complesse, perché altrimenti ci rinuncia in partenza.

Questa cosa ha portato a tutta una serie di scelte che, se viste con gli occhi di chi ha esperienza, sono senz'altro discutibili (... basti pensare solo ai problemi che crea aver nascosto agli utenti main.c , i prototipi delle funzioni, ecc. ecc. ... tutte cose nascoste dal IDE), ma viste da parte della "massa" sono enormi semplificazioni e ... tutto ciò ha un prezzo ...

In realtà, su MCU così piccole è un suicidio usare il C++ (... e ricorda che tu dici C, ma i problemi derivano non dal C, ma dall'uso del C++ perché String in C non esiste) ... ma la cosa ... ha enormemente semplificato la vita alle "masse" che ... se le limitavi al solo ANSII C ... avrebbero incontrato moltissime difficoltà a capire ... ed avrebbero abbandonato !

Purtroppo ... "avere la botte piena e la moglie ubriaca" non è possibile ... :wink:

Vuoi evitare problemi ... programma, come faccio io, in ANSI C ... :grin:

Guglielmo

P.S. : Tu considera che l'altro giorno, un utente, pur di non mettersi "a capire" come ricevere dei caratteri sulla seriale e ricostruire una stringa ... ha preferito usare la Serial.readBytesUntil(), che gli nasconde tutto quello che c'è dietro. Tutto per evitare di dedicare del tempo a studiare ... :roll_eyes:

overmike:
Cio' nonostante mi sento autorizzato a criticare il fatto che se un sistema di sviluppo e/o un linguaggio mette a disposizione una serie di funzionalità, risulti ragionevole pensare di poterle utilizzare senza alcuna riserva. Magari faranno perdere in efficienza la CPU, ma lontano dai miei pensieri sospettare che il loro uso possa compromettere la stabilità del sistema.

Nessuno ti toglie il diritto alla critica, ma devi comunque capire che anche su un computer Pentium con 128 MB di memoria non puoi farci girare un sistema operativo come Windows 8 o Linux/KDE :wink:
Ogni cosa ha i suoi limiti. 2K di RAM sono 2K di RAM, comunque tu li voglia girare. Se hai bisogno di maggior "spazio" ci sono altri prodotti, o la DUE oppure altre MCU a 32 bit di ultima generazione che offrono clock e risorse molto superiori a quelle offerte dall?Atmega328.

PS:
anch'io ho programmato in assembly, tanti anni fa, e capisco che con l'assembly spremi fino all'ultima goccia le risorse di una macchina, ma un linguaggio di più alto livello è quasi indispensabile se non vuoi diventare matto ad usare tutte le periferiche o se devi interfacciarti con HW esterno. Quindi qualche rinuncia o compromesso devi metterli in conto.

@ Leo : Su una cosa però overmike ha ragione ...

overmike:
Mi sono riletto la documentazione delle varie funzioni stringa, e in nessun caso è stato riportato "attenzione all'uso delle funzioni stringa perche' puo creare instabilita".

... se tu puoi segnalare la cosa ... magari nella descrizione di "String" ... una nota che indica di stare attenti all'uso della memoria che fa questa classe ... ci starebbe bene :wink:

Guglielmo

Questo è vero, sentirò se si può inserire una postilla nella pagina:

PS:
però non c'è neanche scritto da nessuna parte che non puoi creare un array di int con 2000 elementi ;). Bisogna avere un minimo di conoscenza di quello che si ha sottomano (non è rivolto a te, ci mancherebbe :sweat_smile:)

leo72:
... però non c'è neanche scritto da nessuna parte che non puoi creare un array di int con 2000 elementi :wink:

Credo siano due cose differenti, anche se ... essendo il prodotto un prodotto di "massa" ... una serie di piccoli "warning" qua e la, nella documentazione, non avrebbero certo fatto male ... ]:smiley:

Però una cosa è ovviamente non tenere conto dei limiti di ciò che si ha, un altra è usare un qualche cosa convinti di stare nei limiti che però, per il suo funzionamento intrinseco, provoca problemi (... es. una String di una dimensione ragionevole a cui però cambi la dimensione N volte) :wink:

Guglielmo

Ok!
Comunque alla fine ci siamo capiti sui discorsi!

Io pero adesso mi do una letta alla string.h e vedo cosa posso tirarne fuori e poi
se ho bisogno mi faccio vivo perchè conto sul vs aiuto.

Renzo

gpb01:
@ Leo : Su una cosa però overmike ha ragione ...

overmike:
Mi sono riletto la documentazione delle varie funzioni stringa, e in nessun caso è stato riportato "attenzione all'uso delle funzioni stringa perche' puo creare instabilita".

... se tu puoi segnalare la cosa ... magari nella descrizione di "String" ... una nota che indica di stare attenti all'uso della memoria che fa questa classe ... ci starebbe bene :wink:

Guglielmo

Nella documentazione mancano altre informazioni che un programmatore esperto comprende, ma che sono motivo di confusione per un principiante, specie se questi non vuole proprio diventare un programmatore perché è più un artista che tecnico, per cui il suo primo obbiettivo e fare funzionare l'applicazione da mostrare. Poi in seguito Arduino viene usato in tutti campi non solo nel campo artistico digitale, con tutte i problemi del caso.

In campo artistico digitale (Arduino è rivolto a questo principalmente) se una applicazione smette di funzionare dopo 10 ore, per qualsiasi ragione non è problema basta resettare. In altri settori applicativi ci sono vincoli molto stretti, non solo il codice non si deve bloccare con "certezza" ma se per caso questa certezza venisse a mancare il codice deve essere rianimato in modo assolutamente trasparente e ciò che è accaduto viene registrato.

Cosa manca nella documentazione:
Funzioni, classi ecc possono essere:

  1. Rientranti o meno.
  2. Usare o meno allocazione dinamica di RAM
  3. Statiche o meno
  4. Virtuali o meno
  5. inline o meno

.... ma c'è sicuramente altro da documentare.

Tuttavia il codice è aperto e si può analizzare, lavoro che richiede competenza.
Il assembly l'allocazione dinamica della memoria è fattibile, tuttavia raramente se ne sente la necessità.

String è un classe comodo, che dovrebbe essere evitata quando tra i requisiti dell'applicazione c'è security constraint, ciò il vincolo della sicurezza. In una applicazione artistica non c'è vincolo di sicurezza, per cui ci basta che l'applicazione lavori per il tempo necessario a mostrarla. In più un reset programmato non comporta alcun danno all'applicazione per è sempre possibile dare l'impressione che l'applicazione stia lavorando continuamente senza problemi.

Come è stato detto ogni cosa ha i suoi limiti e capire quando si è vicini a questi limiti alle volte è complesso, questo è
il caso di String o in genere della allocazione dinamica di memoria, che se applicata con criterio e competenza non comporta alcun problema di alcuna natura. Gli oggetti locali che allocano memoria dinamica al termine del loro scope (ciclo di vita) vengono distrutti e l'eventuale memoria dinamica viene resa disponibile, stessa cosa per gli oggetti che non allocano memoria dinamica che quindi vivono nello stack che diminuisce di dimensioni quando l'oggetto locale esce fuori dallo scope.

In assembly se fai uso di stack (come in C che lo usa implicitamente) non chiami 100 funzioni in cascata, perché lo stack si riempirebbe di un mare di dati ed equivarrebbe ad usare funzioni ricorsive. Il massimo che conviene fare è la chiamata di funzioni in cascata fino a 3-4, poi dipende dall'applicazione.

Le macchine a stati e i toolkit event-driven sono pensati proprio per evitare la chiamata di funzioni in cascata.
Con questi sistemi una funzione al massimo chiama un'altra funzione, ed entrambe emettono segnali che verranno interpretati dal sistema event-driven dopo che queste funzioni ritornano. Si c'è anche qui un problema subdolo: Una funzione che rimane in loop infinito blocca comunque l'intero programma; una funzione che rimane in loop per x tempo impedisce per x tempo la propagazione di eventi. Quindi funzioni di breve durata che terminano subito il lavoro da svolgere, che emetto segnali (o eventi) che verranno interpretati al prossimo ciclo di loop del sistema che gestisce gli eventi. Ovviamente il sistema di gestione degli eventi impegna risorse.

@overmike

Mi si è aperto un nuovo mondo e io che pensavo di aver già imparato qualcosa.

Si, sicuramente ha già imparato qualcosa, ma non è mai abbastanza e raramente è sufficiente :grin:
Ho letto alcune tue risposte in un altro post e mi sono fatto l'idea che aver avuto a che fare con
'assembly ti è servito, per cui consolati hai già una ottima base di partenza, devi solo digerire
la natura subdola del linguaggio C/C++.

Tieni conto che se vuoi, puoi scendere in assembly in due modi:

  • Scrivendo una libreria arduino di cui uno o più compile unit sono scritte in assembly
  • Miscelando nello sketch del codice assembly grazie alla direttiva "asm" che viene detta asm inline,
    in quanto il compilatore durante l'analisi riconosce la direttiva asm e passa ad assemblare più che compilare.

PS: Prendila con filosofia, nulla è perfetto e tutto è migliorabile.

Ciao.

Posso solo aggiungere che per il futuro spero di poter dare e ottenere un aiuto.

A presto

Renzo

FindCmd è la mia soluzione al problema citato. Alla proposta "string.h" ho preferito realizzare ex novo il codice in quanto poi dovrò utilizzarlo per interpretare ulteriori comandi e funzioni.

La funzione "FindCmd" ritorna in "CmdCnt" il codice del comando da 1 a xx
Per un comando non riconosciuto xx = n. dei comandi + 1

Approfittando del tua disponibilita' chiedo:
La lista comandi (CmdLst) puo essere inizializzata in modo piu' visibile.
Ho tentato varie tipologie (tipo '/0'), ma il codice non funzionava

Attendo tue osservazioni, giusto per capire se sono sulla strada giusta.

char RxBuf [] = "123456780";
char RxCar;
int RxCnt;

//                A  B  C    1  2  3    X  Y  Z
char LstCmd [] = {65,66,67,0,49,50,51,0,88,89,90,0,0};
int CmdCnt;
int CmdInd;

void setup () {
  Serial.begin(9600);
  RxCnt=0;
}


void FindCmd(){
  CmdCnt = 1;                          // Codice comando 1-xx  xx+1 = comando non trovato
  CmdInd = 0;                          // Indice per puntare caratteri lista comandi
  RxCnt = 0;                           // Indice per puntare caratteri buffer Rx

  do {
    if (LstCmd[CmdInd] == RxBuf[RxCnt])  RxCnt++;   // se confronto Ok incrementa rispettivi puntatori

    else {
      while (LstCmd[CmdInd] != 0) CmdInd ++;    // cerca terminatore comando (=0)
      CmdCnt ++;                                // incrementa codice comando
      RxCnt = 0;                                // punta 1 carattere buffer Rx
    } // else
  
    CmdInd ++;                                  // incrementa puntatore lista comandi
  
  } while (LstCmd[CmdInd] != 0);                // non raggiunto terminatore comando o termina lista
} // void

overmike:
Approfittando del tua disponibilita' chiedo:
La lista comandi (CmdLst) puo essere inizializzata in modo piu' visibile.
Ho tentato varie tipologie (tipo '/0'), ma il codice non funzionava

Mi pare di si. Si usa escape sequence. \0 direttamente
Qualcosa del genere:

char LstCmd []= "ABC\0123\0XYZ\0\0";

E credo che l'ultimo \0 non serva perchè viene messo in automatico

Avevo già fatto questo tentativo, ma linea codice che mi hai fornito non funziona.
Mi sa che usero' un altro carattere terminatore.