Introduire un String() dans un R"rawliteral(

Bonsoir à toutes et à tous,

J'ai tout un texte HTML compris dans une fonction :

String pageHTML(String val) { return R"rawliteral(
    <!DOCTYPE html>
    <html>
   ...
          <p>Puissance max : <input type="number" id="pMaxMod" value="1000" min="0"> <button onclick="ajusterPmax()">Ajuster</button></p>
...
    </html>
  )rawliteral";
}

Comment faire pour remplacer dans la ligne entre les balises html la valeur "1000" par la valeur val ?

Cordialement.

Pierre.

J'ai trouvé une solution qui consiste à couper la chaîne littérale au droit où on veut placer la variable puis reprendre cette chaîne après comme le montre l'exemple suivant :

float pwA = 1000.5;
void setup() {
  Serial.begin(115200);
  Serial.println(textLit(String(pwA)));
}

void loop() {
}

String textLit(String txt) {
  return R"rawliteral(Ceci est le début : )rawliteral" + txt + R"rawliteral( et ceci est la fin.)rawliteral";
}

Ça fonctionne bien. Je pensais qu'il aurait pu y avoir une syntaxe particulière pour ce faire.

Cordialement.

Pierre.

Vous vous retrouvez avec tout en RAM plusieurs fos.. c’est hyper coûteux en mémoire cette approche…

Si une fonction retourne une String que vous faites

Return "xxx" + String(valeur) + "yyy";

Le compilateur crée (alloue dynamiquement) une String en RAM et y copie xxx depuis la flash
Puis il crée une nouvellle String pour la valeur (dans votre cas c’est fait lors du passage par copie du paramètre) puis une nouvelle String assez grande pour concatener les 2 et effectuée le déplacement de la ram des deux String dans la nouvelle
Puis il alloue une nouvelle String en RAM et y copie yyy depuis la flash
puis une nouvelle String assez grande pour concatener les première concaténation avec celle ci et effectuée le déplacement de la ram des deux String dans la nouvelle
Enfin il y’a potentiellement (suivant les optimisations) une dernier duplication pour retourner cette String complète à l’appelant avant de détruire toutes les Strings intermédiaires qui sont locales à la fonction…

Quand on fait cela on a souvent besoin de mettre à jour ce qui est affiché sur le client web avec ce qu’il y a sur le serveur. Plutôt que de repasser par une requête qui renvoie tout, Personnellement je préfère envoyer un code HTML statique et ensuite le client émet une autre requête Ajax qui vient chercher dynamiquement les champs à mettre à jour. Le client peut envoyer cette requête toutes les X secondes si on veut et seules les variables transitent, la page web se met à jour sans clignoter.

J'ai déjà remarqué que le gâchis de mémoire était votre bête noire :wink:. Vous n'avez pas tord car, comme vous me l'avez déjà montré, ça peut cacher des problèmes de dépassement.

Dans mon cas, si je suis votre raisonnement, mon fichier HTML de 16 ko tronçonné en trois parties et avec potentielle duplication me mène à une occupation d'environ 70 ko. C'est pas bien, mais dans mon application, je dispose de 280 ko. Donc j'ai de la marge.

Pour autant, je vais voir si je peux remplacer ça par une requête Ajax, d'autant plus que je voudrais passer d'autres variables.

Cordialement.

Pierre.

Disons que comme la classe String ne vous dit pas quand ce que vous demandez ne fonctionne pas parce qu'il y a plus de mémoire, quand ça ne crash pas, ça va faire le job à moitié et vous ne saurez pas pourquoi.

D'une manière générale, tant que ça rentre dans la mémoire, pourquoi pas, elle est là et autant s'en servir, mais un jour ou l'autre ça joue des tours et si on a compris ce qu'il se passe, ça permet soit d'anticiper, soit savoir quoi aller regarder pour débug.

Si vous faites cela, la page web est en flash (si vous pensez à mettre const pour le type) et ça n'occupera pas de RAM

Oui, ma page HTML est en mémoire flash

Je n'ai pas compris. De quel type parlez-vous ?

Cordialement.

Pierre.

En reprenant votre explication, je m'aperçois que je n'ai peut-être pas compris. Supposons que :

  • xxx = 1.8 ko
  • String(valeur) = 0.016 ko
  • yyy = 15 ko

Il y a une première allocation A1 de 1.8 ko, puis une deuxième allocation A2 de 0.016 ko, puis une troisième allocation A3 de 1.8 + 0.016 = 1.816 ko.

Maintenant, pour yyy, il y a une quatrième allocation A4 de 15 ko puis une cinquième allocation A5 pour tout regrouper, soit 1.816 +15 = 16.816 ko.

Puis, potentiellement une sixième allocation (duplication) A6 valant aussi 16.816 ko.

Donc, si je comprends votre explication, ces six allocations existent simultanément avant que ne soit détruite la fonction, soit : 1.8 + 0.016 + 1.816 + 15 + 16.816 + 16.816 = 52.264 ko.

C'est cela ou bien est-ce que les allocations temporaires sont détruites au fur et mesure qu'elles ne servent plus ?

Cordialement.

Pierre.

quand vous écrivez ceci

R"rawliteral(Ceci est le début : )rawliteral"

vous avez déclaré une c-string constante et sur ESP32 ce sera mis en flash (sur un Uno ce serait en RAM). C'est comme si vous aviez écrit

const char * debut = "Ceci est le début : ";

juste que le rawliteral permet de ne pas s'ennuyer avec l'échappement des caractères et plusieurs lignes.

Ce que je voulais dire c'est qu'il faut faire attention à la déclaration

si on fait

char debut[] = "Ceci est le début : ";

alors vous avez une version en flash et une version en RAM.


la première allocation est pour String(valeur), mais sinon oui vous avez compris

Avec la classe String vous pouvez optimiser un peu les choses en faisant +=

au lieu de return a + b + c; ou a, b et c sont des Strings (ce qui conduit à toutes les allocation dont on a parlé) il vaut mieux écrire

String r  = a;
r += b;
r += c;
return r;

Si on écrit r = a + b + c; Selon la norme, les temporaires intermédiaires qui ne sont pas stockés dans une variable nommée sont détruits à la fin de l'instruction complète (full-expression). Donc ici ce serait après l'affectation dans r et avant de passer à l'instruction suivante.

Dans votre cas puisque c'est la dernière instruction (return) de la fonction, ça veut dire juste avant de retourner au code appelant.

Ensuite on peut avoir des optimisation, si la classe intermédiaire a implémenté ce qu'on appelle move semantics , le nombre de constructions et destructions effectives peut être réduit : ça permet de transférer les ressources d'un objet temporaire au lieu de les copier, évitant ainsi des allocations inutiles. Cela se fait avec le move constructor (T(T&&)) et le move assignment operator (T& operator=(T&&)) ➜ avec cela un objet peut "voler" les ressources d'un autre en transférant ses pointeurs plutôt qu'en dupliquant ses données. Après un move, l'objet source est laissé dans un état valide mais indéfini.

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