Forum: Mikrocontroller und Digitale Elektronik ESP32 Arduino String Funktion - wo liegt der Fehler?


von Thorsten M. (cortex_user)


Lesenswert?

Hallo,

unten stehender Code soll meine Eigabe über BT einfach parsen und mir 
die entsprechende Datei im SPIFFS auf mein Terminal (Handy) holen. 
Klappt auch aber ich bin wohl mit String etwas durcheinander gekommen. 
Da die String Klasse (und auch Stream) sehr komfortabel sind nutze ich 
sie auch und versuche nur "by reference" zu arbeiten. Probleme mit 
Speicherfragmentierung hatte ich noch nie.

Hier schaue ich, ob nur "show" gesendet wurde oder "show filename" kam. 
Der Fehler wird sichtbar in der Ausgabe. Der dateiname dname ist am Ende 
mit ^@ versehen, also vermutlich nicht Null Terminiert. Ich suche das 
Leerzeichen und zerschneide damit den String. Den zweiten Tag übergebe 
ich der SPIFFS Funktion, die die Datei ausliest. Mit String Filename 
klappt es wo der aktuelle Dateiname global steht, mit dem zerteilten 
String nicht.

Im BT Terminal Programm sende ich Befehle NULL Terminiert ab, das kann 
man ankreuzen ob /r/n oder NULL oder was anderes.

debugln(dname) zeigt "230323.txt^@   an. Erzeugt substring keinen Null 
terminierten Strring?
1
String Filename; /* Enthält aktuellen Dateinamen */
2
3
void HandleBT(String mystring) {
4
  /* Inhalt einer Datei auflisten? */
5
  if (mystring.indexOf("show") != -1) {
6
    int pos = mystring.indexOf(' ');
7
    if (pos != -1) {
8
      String dname = mystring.substring(pos+1); // Hole Dateiname
9
      showTodayFile(&dname); /* Läuft nicht */
10
    } else {
11
      showTodayFile(&FileName); /* Läuft ! */
12
    }
13
    return;
14
  }
15
 usw. usw

Hier noch die Ausgabe Funktion. debug geht direkt auf den BT Comport 
drauf, statt Kabel halt.

1
/* Zeige das heutige File */
2
void showTodayFile(String *dateiname) {
3
4
  /* Datei öffnen */
5
  File file = SPIFFS.open(*dateiname, "r");
6
  if (!file) {
7
    debugln("showTodayFile: Failed to open" + *dateiname + " for reading");
8
    return;
9
  }
10
11
  debugln("Inhalt von " + *dateiname);
12
  String txt;
13
  while (file.available()) {
14
    txt = file.readStringUntil('\r');
15
    debugln(txt);
16
  }
17
  file.close();
18
  debugln("---End of file --");
19
20
  return;
21
}

: Bearbeitet durch User
von Sebastian W. (wangnick)


Lesenswert?

Hab deinen Code nicht nachvollzogen, aber: ^@ könnte die Darstellung 
eines \0-Zeichens durch dein Terminal sein.

LG, Sebastian

von Thorsten M. (cortex_user)


Lesenswert?

Sebastian W. schrieb:
> Hab deinen Code nicht nachvollzogen, aber: ^@ könnte die Darstellung
> eines \0-Zeichens durch dein Terminal sein.

Dann wäre das ja bei den anderen Strings auch so. ist es aber nicht. Ich 
versuche es als letzte Möglichkeit alles in ansi c umzu schreiben mit 
string funktionen aber das ist eben mühsamer.

War eh noch ein Fehler drin, das Root zeichen fehlte
String dname = "/" + mystring.substring(pos+1); // Hole Dateiname

Ist auch nur Gebastel auf Gut Glück....
1
  char eingabe[40];
2
  char name[16];
3
4
  mystring.toCharArray(eingabe, sizeof(eingabe));
5
6
  if (strstr(eingabe, "show")) {
7
    char *p = strstr(eingabe, ' ');
8
    if (pos != NULL) {
9
      strcpy(name, p);
10
      showTodayFile(&name);
11
    }
12
  }

: Bearbeitet durch User
von Sebastian W. (wangnick)


Lesenswert?

Thorsten M. schrieb:
> Dann wäre das ja bei den anderen Strings auch so. ist es aber nicht.

Das meine ich nicht. Wenn ^@ das \0-Zeichen am Stringende sein sollte, 
dann dürfte es gar nicht auf dem Terminal erscheinen. Das könnte nur 
passieren wenn das Längenfeld in der String-Klasse nicht mit dem Inhalt 
des Zeichenkettenpuffers des Strings übereinstimmt. Und das deutet auf 
Speicherkorruption hin. Womöglich existiert der String an der Stelle, wo 
er ausgegeben wird, schon gar nicht mehr?

LG, Sebastian

: Bearbeitet durch User
von Thorsten M. (cortex_user)


Lesenswert?

Sebastian W. schrieb:
> Womöglich existiert der String an der Stelle, wo
> er ausgegeben wird, schon gar nicht mehr?

Wie meinst Du das? Soll ich ihn mal global volatile definieren? Nur zur 
Info. Hänge ich ein '\0' mit dran habe ich zweimal ^@^@ da stehen. Also 
hast Du wohl recht.

String dname;
void HandleBT(String mystring) {

nach außern verlegen klappt aber auch nicht.

Grübel... grübel...

: Bearbeitet durch User
von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

Thorsten M. schrieb:
> und versuche nur "by reference" zu arbeiten.

Wohl nicht so ganz (und ist hier auch nicht wirklich nötig, weil du den 
Wert eh nicht innerhalb der Funktion verändern willst):
- 
https://www.geeksforgeeks.org/passing-by-pointer-vs-passing-by-reference-in-cpp/

Thorsten M. schrieb:
> Hänge ich ein '\0' mit dran

Ich glaube du vermischst hier "Null-Terminierte Char-Arrays" und die 
String-Klasse munter miteinander. Und dann gibt es auch noch einen 
Unterschied zwischen "string" und "String". Bei den Klassen heißt eine 
Null nicht automatisch Stringende.

- 
https://forum.arduino.cc/t/what-is-the-difference-between-std-string-and-string/641910/5
- https://en.cppreference.com/w/cpp/string/basic_string
- 
https://www.arduino.cc/reference/en/language/variables/data-types/stringobject/

von Thorsten M. (cortex_user)


Angehängte Dateien:

Lesenswert?

Irgend W. schrieb:
> Ich glaube du vermischst hier "Null-Terminierte Char-Arrays" und die
> String-Klasse munter miteinander. Und dann gibt es auch noch einen
> Unterschied zwischen "string" und "String". Bei den Klassen heißt eine
> Null nicht automatisch Stringende.

Das hilft mir bei meinem Problem jetzt aber auch nicht weiter. Woher 
diese extra zeichen kommen? Ich benutze String seit jeher und nie gab es 
Probleme damit. Leider habe ich keinen echten Debugger wie beim stm32, 
so dass ich nicht eben ins Ram schauen kann wie es da aussieht.

Die Stringlaenge wird wohl irgendwo im private Teil des Objektes 
stehen,.

Ist übrigens egal ob by reference oder by value, gleiches Ergebnis.
1
  debugln("--------------------------");
2
  debugln("CPU Frequenz    : " + String(ESP.getCpuFreqMHz()) + " Mhz");
3
  debugln("Total Heap      : " + String(ESP.getHeapSize() / 1024) + " kb");
4
  debugln("Free Heap Size  : " + String(ESP.getFreeHeap() / 1024) + " kb");
5
  debugln("Sketch Size     : " + String(ESP.getSketchSize() / 1024) + " kb");
6
  debugln("Free memory     : " + String(ESP.getFreeHeap() / 1024) + " kb");
7
  debugln("Size of Data    : " + String(sizeof(Data)) + "Bytes");
8
  debugln("MAC Adresse     : " + String(WiFi.macAddress()));
9
  debugln("---- Solar Daten ----------");
10
  debugln("UBatt        = " + String(Data.UBatt) + "V");
11
  debugln("UBattAvg     = " + String(AvgValues.UBattAvg) + "V")

: Bearbeitet durch User
von Thorsten M. (cortex_user)


Lesenswert?

Ich gebe für heute auf. Da stimmt was nicht.

show unsinn müsste eine fehlermeldung erzeugen, dass die Datei nicht 
exisiert. Tut es aber nicht, rennt er so drüber. Und der Rest hat 
vermutlich mit dem Terminal zu tun. String können genausogut \r\n 
terminiert sein. Oder eben gar nicht, wenn die Länge irgendwo steht.
1
/* Zeige das heutige File */
2
void showTodayFile(String *dateiname) {
3
4
  String lokal = *dateiname;
5
6
  /* Datei öffnen */
7
  debugln("Oeffne Datei " + lokal);
8
  File file = SPIFFS.open(lokal);
9
  if (!file) {
10
    debugln("showTodayFile: Failed to open " + lokal + " for reading");
11
    return;
12
  }
13
  
14
  while (file.available()) {
15
    debug((char)file.read());
16
  }
17
  file.close();
18
  debugln("---End of file --");
19
20
  return;
21
}

von Εrnst B. (ernst)


Lesenswert?

Thorsten M. schrieb:
> if (mystring.indexOf("show") != -1) {
>     int pos = mystring.indexOf(' ');

Das solltest du nochmal überdenken.
Überleg was bei Eingaben

" show filename"
"show  filename"
"filename show"
"show filename "
usw. passiert.

von Thorsten M. (cortex_user)


Lesenswert?

Εrnst B. schrieb:
> Das solltest du nochmal überdenken.

Ich denke heute nicht mehr und ich habe die Tasten auf Makros liegen, 
Fehler abfangen wird nicht gemacht.

Mit mystring.startWith sucht man am Anfang ....

: Bearbeitet durch User
von Εrnst B. (ernst)


Lesenswert?

Thorsten M. schrieb:
> Ich denke heute nicht mehr

Dann mach wenigstens ganz ohne Nachdenken ein

> if (mystring.indexOf("show") == 0) {

draus.
Edit: Oder startsWith, wie du sagst. Hast ja doch nachgedacht :)

Dann muss "show" immer am Anfang des Befehls stehen, was ja mit deinen 
Makros kein Problem ist.

Und hier:
> File file = SPIFFS.open(lokal);

solltest du angeben, dass du die Datei zum Lesen öffnen willst. Sonst 
wird einfach eine Datei mit falschem Namen angelegt, und dein

> if (!file) {

Check läuft ins Leere.

: Bearbeitet durch User
von Thorsten M. (cortex_user)


Lesenswert?

Εrnst B. schrieb:
> solltest du angeben, dass du die Datei zum Lesen öffnen willst. Sonst
> wird einfach eine Datei mit falschem Namen angelegt, und dein

Nicht ganz. Default ohne Parameter ist lesen. Und gelesen habe ich alles 
was drüber gibt heute, seit 10 Uhr. Sitze hier im Schlafanzug, den 
ganzen Tag nur Kaffee und Kippen. Ah, Berlin Entscheid auch gescheitert, 
sehr gut. Gibt ja noch Vernünftige dort.

Ende für Heute...

von Εrnst B. (ernst)


Lesenswert?

Thorsten M. schrieb:
> Default ohne Parameter ist lesen.

Ah, stimmt. Da hat sich die API ggü. dem ESP8266 geändert.

von Thorsten M. (cortex_user)


Angehängte Dateien:

Lesenswert?

Ich glaube da haben wir den Übeltäter... so sieht das aus was diese 
Zeile erzeugt.

String dname = "/" + mystring.substring(pos + 1) + "txt";

Das BT Terminal stellt diese Null als ^@ dar.

substring scheint entweder fehlerhaft zu sein oder das ist ein Feature, 
dass da eine Null zwischenrutscht. Abgesehen davon dass ich den Punkt 
vergessen habe. Ich werde heute abend mal ne Kanne Kaffee bereitstellen 
und schreibe den ganzen Ramsch auf string Funktionen um, da weiss ich 
wenigstens was bei rauskommt. ChatGPT leistet da auch sehr gute Hilfe.

: Bearbeitet durch User
von Constanze H. (warteschleife)


Lesenswert?


von Thorsten M. (cortex_user)


Lesenswert?

Constanze H. schrieb:
> du scheinst hier String und cstring zu mischen.

Ich kann mit dem Informationsgehalt von Einzeilern leider nichts 
anfangen. Auch wenn das in Chats so üblich ist. Ich mische gar nichts. 
Da rutscht eine Null rein wo keine hin gehört. Ist so!

: Bearbeitet durch User
von Thorsten M. (cortex_user)


Lesenswert?

Das spuckt die Code analyse von ChatGPT aus:

Es sieht so aus, als ob das Problem darin besteht, dass Sie einen 
Pointer auf dname an die Funktion showTodayFile übergeben, obwohl dname 
eine lokale Variable in der if-Anweisung ist. Sobald die if-Anweisung 
beendet ist, wird die Variable dname außerhalb ihres Gültigkeitsbereichs 
fallen gelassen und ihr Speicherplatz wird möglicherweise von anderen 
Daten überschrieben werden. Der Pointer, der an showTodayFile übergeben 
wird, würde daher auf einen ungültigen Speicherbereich zeigen, was zu 
unvorhersehbarem Verhalten führen kann.

Um dieses Problem zu beheben, könnten Sie stattdessen eine Variable vom 
Typ String deklarieren und diese dann an showTodayFile übergeben, 
anstatt einen Pointer auf dname zu übergeben. Hier ist eine modifizierte 
Version des Codes:

von Oliver S. (oliverso)


Lesenswert?

Thorsten M. schrieb:
> String dname = "/" + mystring.substring(pos + 1) + "txt";

Wo genau steht denn diese Zeile in dem gezeigten Code oben?

Thorsten M. schrieb:
> Das spuckt die Code analyse von ChatGPT aus:
>
> Es sieht so aus

ChatGPT sollte besser auch ein paar Tassen Kaffee trinken, wenn sich das 
auf den von dir gezeigten Code bezieht. Wenn du dem natürlich anderen 
Code gegeben hast, kann das durchaus sein.

Oliver

von Thorsten M. (cortex_user)


Lesenswert?

Oliver S. schrieb:
> Wo genau steht denn diese Zeile in dem gezeigten Code oben?
1
/* ---------------------------------------------------------------
2
    Verarbeitet Befehle aus dem Smartphone über BT Terminal 
3
    Die Strings müsse NUL terminiert sein! 
4
---------------------------------------------------------------- */
5
6
7
void HandleBT(String mystring) {
8
9
  /* Inhalt einer Datei auflisten? */
10
  if (mystring.startsWith("show")) {
11
    int pos = mystring.indexOf(" ");
12
    if (pos != -1) {
13
      String dname = "/" + mystring.substring(pos + 1) + ".txt";
14
      debugln("echo:" + dname);
15
      String fileNameWithPath = dname; // create a new String variable to pass to the function
16
      showTodayFile(fileNameWithPath);
17
    } else {
18
      showTodayFile(FileName);
19
    }
20
    return;
21
  }

von Christoph M. (mchris)


Lesenswert?

Thorsten M. schrieb
>unten stehender Code soll meine Eigabe über BT einfach parsen

Nur so eine Frage am Rande: welche Hardware benutzt du auf beiden 
Seiten?

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Thorsten M. schrieb:
> show unsinn müsste eine fehlermeldung erzeugen, dass die Datei nicht
> exisiert. Tut es aber nicht, rennt er so drüber.

Warum sehe ich beim open kein "r"?

Thorsten M. schrieb:
> void showTodayFile(String *dateiname) {

Einen String als Zeiger übergeben?
Scheint mir Unsinnig.

: Bearbeitet durch User
von Julian L. (rommudoh)


Lesenswert?

Ich denke es liegt eher daran, dass der String, den deine 
HandleBT-Funktion übergeben bekommt, am Ende ein NULL enthält. Dann ist 
die NULL natürlich auch im Ergebnis des substring-Aufrufs vorhanden.

An deiner Stelle würde ich prüfen, woher die NULL am Ende kommt. 
Entweder dann an der Aufruf-Stelle ändern oder am Anfang der 
HandleBT-Funktion abschneiden (bzw. trimmen).

von Thorsten M. (cortex_user)


Lesenswert?

Christoph M. schrieb:
> Nur so eine Frage am Rande: welche Hardware benutzt du auf beiden Seiten

Ein Smartphone und einen Esp32. Strings werden mit cr lf jetzt 
terminiert beim Senden. Ich teste das heite abend mal mit String 
Konstanten statt mit dem Terminal Programm

: Bearbeitet durch User
von Constanze H. (warteschleife)


Lesenswert?

open in spiffs erwartet einen cstring aka. const char * path als 
Argument, keinen String. Wie man diesen cstring erzeugt, habe ich im 
post verlinkt. Der Rest ist deine Sache.

von J. S. (jojos)


Lesenswert?

Constanze H. schrieb:
> open in spiffs erwartet einen cstring aka. const char * path als
> Argument, keinen String

Dann würde der Compiler einen Fehler werfen, das wäre in C++ nicht 
erlaubt.
Spiffs ist von FS abgeleitet und erlaubt auch String als path:
https://github.com/espressif/arduino-esp32/blob/0d84018d969309addacbcc3e3782c1fadc95fbc8/libraries/FS/src/FS.h#L95-L96

von Εrnst B. (ernst)


Lesenswert?

Constanze H. schrieb:
> open in spiffs erwartet einen cstring aka. const char * path als
> Argument, keinen String.

Das ESP32-SPIFFS unterscheidet sich von der ESP8266-Variante, bin ich 
oben auch schon drüber gestolpert...
dort gibt's
1
class FS {
2
...
3
   File open(const char* path, const char* mode = FILE_READ, const bool create = false);
4
   File open(const String& path, const char* mode = FILE_READ, const bool create = false);

von Thorsten M. (cortex_user)


Lesenswert?

Constanze H. schrieb:
> open in spiffs erwartet einen cstring aka. const char * path als
> Argument, keinen String. Wie man diesen cstring erzeugt, habe ich im
> post

Ich schätze wirklich jede Hilfe. Aber wenn jemand ohne jeden Nachweis 
falsche Behauptungen aufstellt dann winke ich auch ab. Andere schrieben 
es schon, spiffs ist ein Fs Objekt und das lässt String zu. Rechter 
Mausklick und schon sieht man das. Ich werde das alles trotzdem 
umschreiben ohne String. Ist zwar aufwendiger mit der stdlib aber ich 
weiss da genau was da passiert.

von J. S. (jojos)


Lesenswert?

Julian L. schrieb:
> Ich denke es liegt eher daran, dass der String, den deine
> HandleBT-Funktion übergeben bekommt, am Ende ein NULL enthält. Dann ist
> die NULL natürlich auch im Ergebnis des substring-Aufrufs vorhanden.

sehe ich auch so. Ein String kann '\0' Zeichen enthalten.
1
  String s("test");
2
  s +='\0';
3
  String s2 = s.substring(2);
4
  Serial.println(s2.length());

liefert Länge = 3, die 0x00 ist im String s und Substring s2 enthalten.

von Thorsten M. (cortex_user)


Lesenswert?

J. S. schrieb:
> sehe ich auch so. Ein String kann '\0' Zeichen enthalten.

Yupp... Immer gefährlich zwei Systeme zu mischen. Daher zurück zu den 
Leisten strtok, strstr, strcpy usw

: Bearbeitet durch User
von J. S. (jojos)


Lesenswert?

Der Fehler passiert einfach schon vorher im nicht gezeigten Code.

von Thorsten M. (cortex_user)


Lesenswert?

J. S. schrieb:
> Der Fehler passiert einfach schon vorher im nicht gezeigten Code.

String dname = "/" + mystring.substring(pos + 1) + ".txt";

Hier passiert er! Ich bin es gewohnt, dass der + Overload Operator 
Strings nahtlos zusammen schreibt als Ascii Zeichen. Und das ist eben 
nicht der Fall hier.

von J. S. (jojos)


Lesenswert?

Die Stringklasse funktioniert und es wird 'nahtlos' aneinandergehängt.
Im Aufruf von HandleBT(String) hat String schon eine Null dran pappen.

Gib am Anfang von HandleBT() mal mystring.length() aus und zähle die 
Zeichen.

: Bearbeitet durch User
von Thorsten M. (cortex_user)


Lesenswert?

Läuft!

Da die Befehle eh auf Makro Tasten liegen und feste Größen bestehen ist 
die Null jetzt weg. Die kommt von irgendwoher rein, vermutlich vom BT 
Terminal. Und Kollegen sagten mir auch dass String keinen Terminator 
hat, die Länge wird ständig angepasst von den Methoden, je nachdem wie 
der verwurstelt wird.
1
/* Inhalt einer Datei auflisten? */
2
  if (mystring.startsWith("show")) {
3
    if (mystring.charAt(4) == ',') {
4
      dname = "/" + mystring.substring(5, 11) + ".txt";
5
      showTodayFile(dname);
6
    } else {
7
      showTodayFile(FileName);
8
    }
9
    return;
10
  }

: Bearbeitet durch User
von Sebastian W. (wangnick)


Lesenswert?

Thorsten M. schrieb:
> Und Kollegen sagten mir auch dass String keinen Terminator hat, die
> Länge wird ständig angepasst von den Methoden, je nachdem wie der
> verwurstelt wird.

Da hilft es, den Quellcode von WString.h und auch WString.cpp mal 
ernsthaft zu studieren ...

LG, Sebastian

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.