Problemi usando la classe String ?

A questo punto, anche per i BABBANI come me, suggerisco una parte del forum da dedicare a TEMI SCOTTANTI, come appunto il NON uso di String, l'uso di Millis e altre amenità, in modo, anche, da non ripetere le eterne risposte che si danno a chi entra qui per la prima volta.

Poi capisco che Guglielmo allega i link con la frase "leggi QUESTO e poi QUESTO e poi QUESTO", ma capite che zompettare da un topic all'altro non è semplice.

Per esempio, ho scritto su WeMos D1 (16 Mega) questa parte di programma per inviare dati a MySql e a web, e qui faccio AMPIO uso della classe String. Cosa mi devo aspettare?
Per adesso problemi non ne ho, ma non ne vorrei avere in futuro...

void LeggiGiornale () {
  //-----------------------------  LETTURA GIORNALE
  conta = 1;
  String strG = "";

  for (i = 301; i < 421; i++) {
    ret = node.readHoldingRegisters (i, 16);
    delay (del);
    if (ret == node.ku8MBSuccess) {
      unsigned int nn = node.getResponseBuffer (0);
      if (conta == 1)
      {
        if (i <= 330) strG += "_1c20";
        if (i > 330 && i <= 360) strG += "_2c20";
        if (i > 360 && i <= 390) strG += "_3c20";
        if (i > 390 && i <= 420) strG += "_4c20";
        strG += nn;
        strG += "-";
      }
      else if (conta == 2)
      {
        strG += nn;
        strG += "-";
      }
      else if (conta == 3)
      {
        strG += nn;
        strG += "s";
      }
      else if (conta == 4)
      {
        strG += nn;
        strG += "a";
      }
      else if (conta == 5)
      {
        strG += nn;
        strG += "a00f";
      }
      else if (conta > 5)
      {
        strG += nn;
        strG += "_";
      }
    }
    else Serial.print ("Error G ");
    if (conta == 10) conta = 0;
    conta++;
  }

  strG.replace("_1c200-0-0s0a0a00f0_0_0_0_0_", "");
  strG.replace("_2c200-0-0s0a0a00f0_0_0_0_0_", "");
  strG.replace("_3c200-0-0s0a0a00f0_0_0_0_0_", "");
  strG.replace("_4c200-0-0s0a0a00f0_0_0_0_0_", "");

  // --------------------------- to MySql WEB
  digitalWrite (2, LOW);
  strURL = "GET /importG.php?s=";
  strURL += serialnumber;
  strURL += "_";
  strURL += strG;
  strURL += " HTTP/1.1";
  client.println(strURL);
  client.println("Host: www.xxxxxxxxxx.info");
  client.println("User-Agent: Arduino 1.0\r\n\r\n");
  Serial.println (strURL);
}
// --------------------------- W E B -----------------------------------------------

void importVARIABILI() // -------------------- INVIA VARIABILI A MySQL
{
  strURL = "GET /import.php?s=";
  strURL += serialnumber;
  strURL += "&t=";
  strURL += strD;
  strURL += " HTTP/1.1";
  client.println(strURL);
  client.println("Host: www.xxxxxxxxxx.info");
  client.println("User-Agent: Arduino 1.0\r\n\r\n");
  Serial.println (strURL);
  var1 = 1;
}
void importSTATO() // ------------------------ INVIA STATI A MySQL
{
  strURL = "GET /importS.php?s=";
  strURL += serialnumber;
  strURL += "&d=";
  strURL += strXY;
  strURL += " HTTP/1.1";
  client.println(strURL);
  client.println("Host: www.xxxxxxxxxx.info");
  client.println("User-Agent: Arduino 1.0\r\n\r\n");
  Serial.println (strURL);
}

void funclic()
{
  strURL = "GET /public/AA/";
  strURL += serialnumber;
  strURL += ".txt HTTP/1.1";
  client.println(strURL);
  client.println("Host: www.xxxxxxxxxx.info");
  client.println("User-Agent: Arduino 1.0\r\n\r\n");
  String c = client.readString();

  int f = c.length();
  int v = 1;
  int g = f - v;

  if (c.substring(g, f) == "1")
  {
    if (var1 == 0) {
      LeggiVariabili ();
      var1 = 1;
    }
    for (ii = 1; ii <= 20; ii++) {
      LeggiStati ();
      delay (800);
    }
  }
  else var1 = 0;
}

void modificasetup()
{
  strURL = "GET /public/AA/PRO_";
  strURL += serialnumber;
  strURL += ".txt HTTP/1.1";
  client.println(strURL);
  client.println("Host: www.xxxxxxxxxx.info");
  client.println("User-Agent: Arduino 1.0\r\n\r\n");

  String c = client.readString();

  int f = c.length();
  int v = 1;
  int g = f - v;

  if (c.substring(g, f) == "*") {
    v = 10;
    g = f - v;
    String dd = c.substring(g, f);
    variabile = dd.substring(0, 3);

    if (dd.substring(4, 5) != 0) valore = dd.substring(4, 9);
    else if (dd.substring(5, 6) != 0) valore = dd.substring(5, 9);
    else if (dd.substring(6, 7) != 0) valore = dd.substring(6, 9);
    else if (dd.substring(7, 8) != 0) valore = dd.substring(7, 9);
    else if (dd.substring(8, 9) != 0) valore = dd.substring(8, 9);

    // --------------------------- SCRITTURA VARIABILE D  -------------
    ret = node.writeSingleRegister (variabile.toInt(), valore.toInt());
    delay (del);
    Serial.print ("Scrivo D"); Serial.print (variabile); Serial.println (valore);
    var1 = 0;
    funcfatto();
  }
}

void funcfatto()
{
  strURL = "GET /modifica.php?s=";
  strURL += serialnumber;
  strURL += " HTTP/1.1";
  client.println(strURL);
  client.println("Host: www.xxxxxxxxxx.info");
  client.println("User-Agent: Arduino 1.0\r\n\r\n");

  LeggiVariabili ();
}

Steve, il thread "Classe "SafeString" una valida alternativa alla pessima "String" ..." è dedicato SOLO a informazioni su detta libreria, per domande come la tua e bene aprire un thread dedicato e NON usare quello. Grazie.

Ho diviso io e creato questo thread :wink:

Guglielmo

Grazie Guglielmo. :slight_smile:

Purtroppo tu usi la classe String nel peggior modo possibile, riempiendo e svuotando in continuazione, con nuove cose, lo stesso oggetto String ... ::slight_smile:

Su ESP la memoria abbonda e prima di arrivare ad una condizione di crash ce ne vuole, però, se vuoi stare tranquillo, hai tre strade ...

1. la più faticosa (... ma la più educativa), abbandonare la classe String e riscrivere tutto usando le stringhe classiche del 'C' e le funzioni che trovi in <string.h>

2. molto meno faticosa e comunque sicura, studiarsi la SafeString che, nell'uso è molto simile alla classe String, è però sicura sotto l'aspetto uso della memoria.

3. la più banale e quella che mi piace meno, sapendo a priori quale è la lunghezza massima che può assumere il tuo oggetto String, preallocarlo staticamente all'inizio per la sua massima dimensione usando il metodo reserve() che, in teoria, dovrebbe evitare i noti problemi (... dico in teoria perché, NON usando la classe String, non l'ho mai potuto mettere alla prova per vedere se veramente risolve i problemi).

Guglielmo

Il core esp8266 offre delle comode funzioni per monitorare ram disponibile e stato "frammentazione".

Per quella che è la mia esperienza, se usata con cognizione la classe String con questa mcu non crea alcun problema e non comporta frammentazione.
Ho monitorato per giorni interi l'andamento: memoria libera e frammentazione rimangono costanti nel tempo.

cotestatnt:
... se usata con cognizione la classe String ...

... se usata con cognizione non dovrebbe mai creare problemi, purtroppo, spesso è usata senta troppe considerazioni sulla memoria e quindi ... continuo a sconsigliarla ... così si evitano del TUTTO i problemi.

Essendoci poi ora la SafeString, che offre le stesse funzionalità con tutta sicurezza, non capisco
l'insistere a voler usare una cosa che può dare problemi ... ::slight_smile:

Guglielmo

gpb01:
non capisco l'insistere a voler usare una cosa che può dare problemi

Guglielmo perdonami, ma la classe String è ampiamente utilizzata sia nel core Arduino-ESP8266 vero e proprio, che nelle librerie incluse al core stesso.
Che senso ha aggiungere (e dover gestire) un'ulteriore dipendenza ai propri progetti se non è strettamente necessaria e funzionale?
Sarebbe come dire, siccome non sai andare tanto bene in bicicletta e ti puoi far male, ti do il triciclo e devi usare solo questo!
Mi sono tolto la curiosità e facendo una ricerca nei file sorgenti dell'ultima versione del core Arduino-ESP8266 vengono fuori quasi 700 occorrenze di "String".
Ci preoccupiamo se usare o meno una variabile locale in una funzione quando basta includere un* ESP8266WebServer.h* o ESP8266HTTPClient.h (tanto per dire due librerie comunemente usate) e avere comunque l'uso di String che rientra dalla finestra?
Francamente non ne vedo il senso né tantomeno l'utilità. Allora a questo punto tanto vale usare direttamente il framework esp-idf rinunciando alla grande portabilità del codice "arduino like".

the String class is widely used both in the Arduino-ESP8266 core itself, and in the libraries included in the core itself.

Very true and very sad. I am struggling with an ESP32 internet project that just hangs after some time.

My point would be why make a poor situation worse by using Strings in your own processing code.

Arduino Strings, as well as adding the memory fragmenation also has a few odd errors. Using SafeStrings avoids those and provides detailed error msgs to help you find your coding problems that cause crashes. The ESP crash dumps are very useful, but the SafeString error messages are more precise and detailed.

Traduzione fatta con Google Translate:

Molto vero e molto triste. Sto lottando con un progetto Internet ESP32 che si blocca dopo un po 'di tempo.

Il mio punto sarebbe perché peggiorare una situazione povera usando le stringhe nel tuo codice di elaborazione.

Arduino Strings, oltre ad aggiungere la frammentazione della memoria, presenta anche alcuni strani errori. L'utilizzo di SafeStrings li evita e fornisce messaggi di errore dettagliati per aiutarti a trovare i tuoi problemi di codifica che causano arresti anomali. I dump di arresto anomalo ESP sono molto utili, ma i messaggi di errore di SafeString sono più precisi e dettagliati.

Getting back to original question
At the end of this post is the code revised to use SafeStrings.

Basically
a) chanage Strings to SafeStrings (use createSafeString () or cSF () macros)
b) add SafeString :: setOutput (Serial); in setup() to display error msgs. Errors are caught regardless.
c) for substring () pass in the result, eg dd.substring (value, 4.9);
d) use SafeString toInt (varInt) to check for valid integers

The biggest change is for String c = client.readString ();
The readString () continually re-allocates memory for every char read and is also a blocking call
until the read times out

All SafeString methods are non-blocking so I have added a (non-blocking) timeout

Traduzione fatta con Google Translate

Tornando alla domanda originale
Alla fine di questo post c'è il codice rivisto per utilizzare SafeStrings.

Fondamentalmente
a) cambia le stringhe in SafeStrings (usa le macro createSafeString () o cSF ())
b) aggiungere SafeString :: setOutput (Serial); in setup () per visualizzare i messaggi di errore. Gli errori vengono rilevati indipendentemente.
c) per la sottostringa () passare nel risultato, ad esempio dd.substring (value, 4.9);
d) utilizzare SafeString toInt (varInt) per verificare la presenza di interi validi

Il cambiamento più grande è per String c = client.readString ();
ReadString () riassegna continuamente la memoria per ogni carattere letto ed è anche una chiamata di blocco
fino al termine della lettura

Tutti i metodi SafeString non sono bloccanti, quindi ho aggiunto un timeout (non bloccante)

 cSF (c, 20); // 20 looks like enough
  millisDelay readTimeOut;
  readTimeOut.start (2000); // 2 sec timeout
  while (! readTimeOut.justFinished ()) {// not timed out
    if (c.read (client)) {// read from client into SafeString c, return true if char read
      readTimeOut.restart (); // restart time out after each char read
    }
    if (c.isFull ()) {// no more space to read
      // print an error msg if setOutput () has been called
      SafeString :: Output.println ("Error: Client read () filled SafeString c");
      break; // continue with what we have
    }
  }

The revised code in attach ...

Traduzione fatta con Google Translate

Il codice rivisto in allegato ...

code.ino (7.05 KB)

cotestatnt:
Guglielmo perdonami, ma la classe String è ampiamente utilizzata sia nel core Arduino-ESP8266 vero e proprio ...

... allora, te l'ho detto e ripetuto cento volte, TU fai come ti pare, qui agli utenti insegnamo a NON usarla perché è una porcheria. Il fatto che sia purtroppo molto usata nei vari "core" è una questione di, perdonami, "pigrizia" di chi li ha scritti, perché trova comodo usare certe porcherie che, certamente, velocizzano tante cose ma che, tanto per dirne una, sono proprio quelle che rendono il mondo Arduino (ESP incluso) inaffidabile ed il software NON certificabile in certi ambienti.

Ripeto, TU la vuoi usare, usala, ovviamente nessuno te lo proibisce, ci mancherebbe :slight_smile: , ma qui, dove il 90% è gente che programma per diletto personale e NON programmatori esperti che sanno usare certe cose in modo sempre corretto, si insegna altro ... e NON vorrei tornare più sul discorso. Grazie.

Guglielmo

drmpf:
Arduino Strings, as well as adding the memory fragmenation also has a few odd errors.

The Arduino String class implementation in ESP8266 core is not exactly the same as AVR Arduino but some improvements was added expecially from the memory managment point of view due the support for SSO (Small String Optimization).
Anyway, I try always to limit the use of String and if max size of strings is well knowed, usually I prefer standard C string.
Your excellent library, do a lot of other useful and convenient stuffs and can be a good choice if needed.
But sometime (luckily fews), dynamic allocation with String can be useful in order to not waste a large amount of memory.
After months of analyzing and testing memory usage in my projects, at the end I've not found really big advantages absolutely avoinding it so I decided stop annoying myself and use String with the necessary precautions. None of my projects with ESP chipset had problem with memory fragmentation or unwanted reset since years.

Traduzione fatta con Google Translator:

L'implementazione della classe Arduino String nel core ESP8266 non è esattamente la stessa di AVR Arduino, ma sono stati aggiunti alcuni miglioramenti soprattutto dal punto di vista della gestione della memoria grazie al supporto per SSO (Small String Optimization).
Comunque, cerco sempre di limitare l'uso di String e se la dimensione massima delle stringhe è ben nota, di solito preferisco la stringa C standard.
La tua eccellente libreria, fa molte altre cose utili e convenienti e può essere una buona scelta se necessario.
Ma a volte (fortunatamente pochi), l'allocazione dinamica con String può essere utile per non sprecare una grande quantità di memoria.
Dopo mesi di analisi e test dell'utilizzo della memoria nei miei progetti, alla fine non ho riscontrato dei grossi vantaggi nell'evitarla assolutamente, quindi ho deciso di smetterla di annoiarmi e di utilizzare String con le dovute precauzioni. Nessuno dei miei progetti con chipset ESP ha avuto problemi con la frammentazione della memoria o il ripristino indesiderato da anni.

gpb01:
... allora, te l'ho detto e ripetuto cento volte, TU fai come ti pare, qui agli utenti insegniamo a NON usarla perché è una porcheria

Non voglio creare polemiche e cerco di pormi con tutto il rispetto dovuto, ma dovrei forse sottointendere che gli utenti del forum non possono dare consigli diversi dal pensiero "mainstream" o "admin approved"?
Io NON sono un programmatore esperto, faccio altro di mestiere, e quel poco che ho imparato sul mondo embedded (che per me è solo un hobby) è frutto di studio, approfondimenti e tanti tanti "esperimenti". Non capisco perché altri non dovrebbero intraprendere percorsi simili per partito preso.
Di sicuro non tornerò più sul discorso, ma francamente sono un po' amareggiato.

cotestatnt:
Non voglio creare polemiche e cerco di pormi con tutto il rispetto dovuto, ma dovrei forse sottointendere che gli utenti del forum non possono dare consigli diversi dal pensiero "mainstream" o "admin approved"?

Certo che possono, ci mancherebbe, ma, se gente che ha decenni di esperienza e sa riconoscere le "criticità", che ha incontrato centiania di utenti e di problemi e che conosce da anni i guai che vengono fuori, dice che le cose si fanno in un certo modo, tu puoi dire che TU le fai in un altro, ma NON consigliare quello che qui tutti riteniamo errato, fuorviante e causa di problemi.

Spero di aver chiarito il tuo dubbio e, ripeto, NON vorrei tornare più sulla questione String ...
... qui, praticamente tutti (inclusa gente esperta come quelli di Adafruit e Sparkfun, che non soino esattamente gli ultimi arrivati), riteniamo NON vadano consigliate (... non dimenticare che il 70% degli utenti usa ancora Arduino UNO con 2KB di SRAM). Grazie :slight_smile:

Guglielmo

cotestatnt:
The Arduino String class implementation in ...

Scusa, per curiosità, hai il link alla pagina di questo testo ? Grazie
P.S. se non hai un S.O. che fa garbage collection (o un linguaggio interpretato come Java e la sua JM), ci saranno sempre possibilità di avere memoria frammentata.

nid69ita:
Scusa, per curiosità, hai il link alla pagina di questo testo ?

No, nessun testo o link. Ho aperto i due sorgenti e li ho confrontati side by side.
La classe String è definita in WString.h / WString.cpp
Le differenze più evidenti sono nelle funzioni "base" di memory management (in particolare la riallocazione del buffer di memoria) e nelle funzioni copy and move dove al posto di strcpy viene usato principalmente memmove_P.

Wow che fior fiore di esperti.
La cosa non può che farmi piacere e ringrazio veramente per la "consulenza".
Si, ho visto il post sulla Safestring e mi riprometto di leggermelo bene.
Diciamo che con 16 Mega di memoria non mi sono fatto più di tante "torte", però se voglio dare un buon prodotto è meglio che questo abbia buone basi.

Per ciò che riguarda la String, come tutti i "novellini" la trovo sulla guida di riferimento dell'IDE:

String
Description

The String class, part of the core as of version 0019, allows you to use and manipulate strings of text in more complex ways than character arrays do. You can concatenate Strings, append to them, search for and replace substrings, and more. It takes more memory than a simple character array, but it is also more useful.

For reference, character arrays are referred to as strings with a small s, and instances of the String class are referred to as Strings with a capital S. Note that constant strings, specified in "double quotes" are treated as char arrays, not instances of the String class.

...con tutti quei sottocomandi che uno, come li vede, si mette le mani nei capelli (chi li ha...).

Il problema che, sempre da "novellino", non leggo controindicazioni ne, tantomeno, suggerimenti, e questo non aiuta chi vuole imparare bene se poi trova che quello che ha studiato e che ha messo in pratica è tutto da buttare. Quantomeno un "avviso" sotto a quel comando ci vorrebbe... (ma è solo un mio suggerimento).

Se avete trovato qualche criticità peggiore di altre nel programma che ho allegato gradirei avere consigli su come "aggirarle", anche continuando ad usare String, ma non perché la voglio usare, ma perché vorrei conoscere meglio cosa NON fare quando si usa anche solo per due righe di programma.

Buona serate e... non litigate :slight_smile:

See my tutorial on Taming Arduino Strings for guidelines for successfully using Arduino Strings.
On Uno and Mega2560 it turns out Strings are essentially bullet proof.
On other boards with more memory follow the guidelines to using Arduino Strings, i.e.

  • Declare long lived Strings as globals and reserve () space in setup () , starting with the smallest to the largest. Check the return from the last largest reserve () to see that you have enough memory for all the Strings
  • If you have created Strings in the loop () method, they are long lived, move them to Globals and repeat step 1.
  • Pass all Strings arguments to methods, as const String & . For results pass a String & result, that the method can update with the result. ie void strProcessing (const String & input1, const String & input2, ,, String & result) {..} See Using String & for arguments
  • Set the IDE File → Preferences Compiler Warning to ALL and watch for warning: passing 'const String' as 'this' argument discards qualifiers [-fpermissive] messages in the compile window. This says you are trying to modify a const String & arg.
  • Do string processing in small compact methods. In these methods use local Strings in preference to local char []. These methods will completely recover all the heap and stack used by their local Strings and other variables on exit. See String versus char []
  • Do not use the String + operator at all and avoid using the String (...) constructors as these create short lived temporary Strings. Use = and + = operators or concat (), as in result + = "str"; result + = 'c'; result + = number; etc. See Minimizing Memory Usage
  • To monitor for Low Memory and fragmentation, add a StringReserveCheck to at least the last largest String reserve () . Add StringReserveCheck to other Strings as necessary. See Using StringReserveCheck (download and install StringReserveCheck.zip )
  • If your Arduino program uses the Web eg ESP32 / EPS8266 webserver / httpClient, then the underlying web support libraries already use lots of Strings. If you project is suppose to run for a long time, add a periodic automatic reboot. See ESP32 / ESP8266 Adding Periodic Automatic Reboots

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.