Go Down

Topic: Ottimizzare la gestione delle stringhe (Read 10542 times) previous topic - next topic

Maurotec

#15
Feb 27, 2014, 03:58 pm Last Edit: Feb 27, 2014, 04:11 pm by MauroTec Reason: 1

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


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 ;)

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
Quote
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 :smiley-mr-green:
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.


overmike

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

A presto

Renzo

overmike

#17
Mar 03, 2014, 06:40 pm Last Edit: Mar 03, 2014, 06:43 pm by overmike Reason: 1
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.

Code: [Select]


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




nid69ita

#18
Mar 03, 2014, 08:04 pm Last Edit: Mar 03, 2014, 08:06 pm by nid69ita Reason: 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

Mi pare di si. Si usa escape sequence. \0 direttamente
Qualcosa del genere:
Code: [Select]
char LstCmd []= "ABC\0123\0XYZ\0\0";
E credo che l'ultimo \0 non serva perchè viene messo in automatico
my name is IGOR, not AIGOR

overmike

#19
Mar 03, 2014, 08:35 pm Last Edit: Mar 03, 2014, 08:37 pm by overmike Reason: 1
Avevo già fatto questo tentativo, ma linea codice che mi hai fornito non funziona.
Mi sa che usero' un altro carattere terminatore.


nid69ita

#20
Mar 03, 2014, 08:53 pm Last Edit: Mar 04, 2014, 12:47 am by nid69ita Reason: 1
Si okay, ora a posto:
Code: [Select]
char LastCmd[]= "ABC\0""123\0""XYZ\0""\0";
void setup() {
 Serial.begin(9600);
 Serial.println(sizeof(LastCmd));
 for(int i=0;i<sizeof(LastCmd);i++)
 { Serial.print(LastCmd[i]); Serial.print(" "); Serial.println(i);  
 }  
}
void loop(){}

Devi mettere dopo il \0 l'apice doppio e riaprire (se necessario proseguire) i restanti caratteri. Altrimenti un \0123  interpreta come un singolo carattere \012 ovvero carattere 12 e poi la singola lettera 3

P.S. per fare ricerche e confronti tra stringhe è utile strncmp(), di sicuro più veloce di un tuo ciclo for o while
http://www.cplusplus.com/reference/cstring/strncmp/?kw=strncmp
http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html#ga36cc0ab27fbcc70615214170ae79fbf7
my name is IGOR, not AIGOR

Maurotec

#21
Mar 03, 2014, 11:27 pm Last Edit: Mar 04, 2014, 08:06 am by leo72 Reason: 1
Una define può creare una costante letterale:

Code: [Select]
#define CMD_START   "123"
#define CMD_STOP     "456"
#define CMD_END      "789"

char *LastCmd[] = {
      CMD_START  // qui il tuo commento
    , CMD_STOP   // ecc
    , CMD_END    // ecc
                                  };


Nota che LastCmd è un array di puntatori a carattere e CMD_START ecc terminano già con null \0.

Consiglio:
Il nome più adatto per LastCmd, è lastCommands.
Per convensione:
Il primo carattere maiuscolo è riservato ai tipi classi o struct.
Una variabile comincia sempre in minuscolo e se è una lista termina al plurale "commads"
Certo se la chiami list non serve la "s" finale, al più potrebbe significare lista di liste.
Le costanti letterali, numeriche sia tramite define o tramite valiabile "const" si scrivono in maiscolo.

Gli enumerati (enum) per differenziarli dalle classi o struct e visto che sono constanti numeriche
si scrivono maiuscolo, oppure come le classi o struct e sai già che quello è una classe, una struct o un enumerato.

In C (no C++) le convenzioni sono simili, i tipi di sottosistema terminano con _t, come size_t, wchar_t ecc.

Comunque e tutto molto soggettivo e basta scegliere un modo che crei meno equivoci.
Questo libro potrebbe interessarti: http://books.google.it/books?id=k-AqxJ3_zbUC&pg=PA32&lpg=PA32&dq=programmazione+stili+nomi&source=bl&ots=R_7U_4bYVM&sig=4J-_Y53vM8rRhh6TqfZNGEtdrfU&hl=it&sa=X&ei=RwMVU_vyMpLd7QaE84D4BQ&ved=0CE8Q6AEwBQ#v=onepage&q=programmazione%20stili%20nomi&f=false

Ciao.



overmike


Resto sempre più sorpreso dai vostri post in arrivo.
Davvero questo C non ha limiti sia nelle funzioni che nella sintassi del linguaggio.
Mille modi per sviluppare lo stesso codice.

Mi sono dato un'occhiata alle funzioni "strcmp()" e "strncmp" e fatto due tre prove con una serie di valori,
ma non sono riuscito ad ottenere l'equivalente di "String.indexOf ( str1,str2)", cioe' ricavare la posizione alla quale la str2 corrisponde a str1, per capirci la "Instr" di VB.
Nell'analizzare string.h ho trovato spunti davvero interessanti per sviluppi anche futuri.

Ringrazio per la chiccherata.

Renzo



gpb01


ma non sono riuscito ad ottenere l'equivalente di "String.indexOf ( str1,str2)", cioe' ricavare la posizione alla quale la str2 corrisponde a str1, per capirci la "Instr" di VB.
Nell'analizzare string.h ho trovato spunti davvero interessanti per sviluppi anche futuri.


Beh ... lo devi aver analizzato un po' di fretta ... prova a guardare strstr() e strtok() ... che si usano appunto per fare il parse di stringhe alla ricerca di token ;)

Guglielmo
Search is Your friend ... or I am Your enemy !

overmike

Nello scorrere in modo veloce la lista, avevo cercato funzioni che restituissero valori numerici, non accorgendomi delle funzioni da te segnalate che invece ritornano un puntatore sottoforma di char.

La funzione che poteva fare al caso mio era la strstr, ma invece di un indice mi ritorna, come detto, un puntatore in memoria.
Mi sono guardato in internet una serie di esempi e anche ben fatti, ma piu' mi addentro nel C e piu' mi creo confusione e divento inconcludente.

All'evidenza dei fatti sono giunto alla decisione di far tesoro dei  vs post, ma si sospendere per il momento l'approfondimento del C per dar spazio alla realizzazione del mio progetto.
Vedro' nel futuro di riprendere la cosa.

A risentirci in queste pagine.

Renzo



gpb01


La funzione che poteva fare al caso mio era la strstr, ma invece di un indice mi ritorna, come detto, un puntatore in memoria.
Mi sono guardato in internet una serie di esempi e anche ben fatti, ma piu' mi addentro nel C e piu' mi creo confusione e divento inconcludente.


In C il saper maneggiare i pointers è una cosa fondamentale e di una potenza incredibile.

Quando avrai voglia scaricati da QUI questo prezioso libro (... dedicato ai pointers) e studiatelo ... ne scoprirai delle belle :)

Guglielmo

P.S. : Il link che ti ho dato è legale purché il libro venga scaricato per tuo esclusivo uso personale.
Search is Your friend ... or I am Your enemy !

overmike

Magari la cosa vi fara sorridere.
E' una banale funzione per la conversione di un byte in una striga alfanumerica esadecimale, comprensiva degli zero non significativi.
A seguito dei vari posts,  ho preso per mano questa routine e mio dire penso di averla ottimizzata sostituendo una serie di funzioni Stringa con char (non oso farvi vedere l'originale).

Code: [Select]

char* HexOut (byte VarVal){

  byte v;
  char hex []="FF";

  v = VarVal >> 4;           // sposta nible alto (7-4) a (3-0)                   
  if (v > 9) v += 7;         // 10-15 > A-F
  hex [0]= v + 0x30;         // conv. in alfan. e salva
 
  v = VarVal & 0xF;          // isola nible basso
  if (v > 9) v += 7;         // 10-15 > A-F
  hex [1]= v + 0x30;         // conv. in alfan. e salva
  return hex;
} //void hexout



gpb01

#27
Mar 04, 2014, 08:09 pm Last Edit: Mar 04, 2014, 08:11 pm by gpb01 Reason: 1
Carina ma ... c'è un problema ... potrebbe NON funzionare ...

la variabile hex la allochi dinamicamente quando entri nella funzione e ... quando esci quella variabile viene distrutta e la sua area di memoria può venire sovrascritta con ... ovvie conseguenze (... e t'assicuro capita, oh se capita  XD).

Due soluzioni ...
... uno, dichiara hex fuori dalla funzione così, anche quando si esce, continua ad esistere
... due, dichiarala dentro la funzione ma con l'attributo "static", così non viene distrutta :

Code: [Select]
static char hex[3];

e la inizializzi all'entrata nella funzione con :

Code: [Select]

  hex[0] = 'F';
 hex[1] = 'F';
 hex[2] = 0x00;


Guglielmo
Search is Your friend ... or I am Your enemy !

Maurotec

Quote
la variabile hex la allochi dinamicamente quando entri nella funzione e ... quando esci quella variabile viene distrutta e la sua area di memoria può venire sovrascritta con ... ovvie conseguenze (... e t'assicuro capita, oh se capita  smiley-lol).


Come dice gpb01, il problema c'è, ma si è spiegato.....mmmm.........come dire di...vabbè :smiley-mr-green:

All'interno di un blocco di codice {} il contesto è locale e tutto ciò che viene creato in locale termina di esistere una volta che si è fuori contesto. Quindi la hex è locale e vive nello stack C, al termine della funzione lo stack ritorna ad essere quello che era prima che di entrare nella funzione o comunque viene modificato per salvare il dato restituito dalla funzione nel caso di funzioni non void.

Mentre se all'ingresso della funzione allocassi la memoria dinamicamente il puntatore restituito sarebbe ancora valido, in quanto sia l'allocazione e seguente de-allocazione di memoria spetta al programmatore, ciò vuol dire che se allochi memoria e poi non la liberi e te ne dimentichi o perdi il puntatore a questa introduci quello che chiamiamo memory leak. Mentre se non ti perdi il puntatore, quella porzione di memoria conterrà sempre ciò che gli hai salvato fino a che non esegui un free(pointer) per C, o delete pointer per il C++.

Quote
Resto sempre più sorpreso dai vostri post in arrivo.
Davvero questo C non ha limiti sia nelle funzioni che nella sintassi del linguaggio.
Mille modi per sviluppare lo stesso codice.


Il C++ ti sorprenderà ancora di più, pensa alla possibilità di ridefinire gli operatori unari ++ -- e grazie a questo
che String si può permettere la concatenazione objString += aString;

Ciao.

gpb01


Come dice gpb01, il problema c'è, ma si è spiegato.....mmmm.........come dire di...vabbè :smiley-mr-green:


:smiley-mr-green: ... la mia era ovviamente una spiegazione atta a semplificare al massimo, poi ...
... se overmike vuole approfondire  si studia la gestione della memoria QUI e QUI e si chiarisce le idee ...  XD XD XD

Guglielmo
Search is Your friend ... or I am Your enemy !

Go Up