Unterprogramm liefert nicht nachvollziehbares Resultat

Ich möchte in einer Zeichenkette die Leerstellen in %20 wandeln. Im Prinzip sollte das mit dem nachfolgenden Sketch gehen.

Es funktioniert aber nur, wenn ich die serielle Ausgabe in der Routine mache. So wie es jetzt ist kommt nur Müll. Oder ich mach buffer global. Dann geht es auch. Aber warum nicht so?

void setup() {
  Serial.begin(115200);
  Serial.println(leerErsetzen("Pin 5 alarm"));
}

char* leerErsetzen(char* text)
{
  char buffer[200];   //ziel
  int leseZeiger = 0;
  int schreibZeiger = 0;

  for (leseZeiger = 0; leseZeiger <= strlen(text); leseZeiger++)
  {
    if (text[leseZeiger] != 0x20)
    {
      buffer[schreibZeiger++] = text[leseZeiger];
    }
    else
    {
      buffer[schreibZeiger++] = '%';
      buffer[schreibZeiger++] = '2';
      buffer[schreibZeiger++] = '0';
    }
    buffer[schreibZeiger + 1] = 0;
  }
//Serial.println(buffer);   
  
  return (buffer);
}
void loop() {}

Dass man keine lokalen Variablen zurückgeben kann solltest du eigentlich wissen. Deren Speicher wird am Ende der Funktion freigegeben.

Du musst den Puffer außerhalb anlegen und als Parameter übergeben. Sowie es jede Standard C Funktion dieser Art macht. Der Puffer muss nicht unbedingt global sein. Der kann auch nur in loop() oder einer anderen Funktion existieren. Aber er darf nicht lokal in leerErsetzen() sein.

Nachtrag:
Korrekt sollte es heißen dass man keine Zeiger oder Referenzen auf lokale Variablen zurückgeben kann. Per value Rückgabe geht natürlich. Aber hier hast du nur einen Zeiger.

Warum machst du nicht

void leerErsetzen(char* old, char* new);

Serenifly:
Aber er darf nicht lokal in leerErsetzen() sein.

Nachtrag:
Korrekt sollte es heißen dass man keine Zeiger oder Referenzen auf lokale Variablen zurückgeben kann. Per value Rückgabe geht natürlich. Aber hier hast du nur einen Zeiger.

Nein, wusste ich nicht. Ich hab die Routine irgendwoher und da war ein serielles Debug drin und es ging immer. Nun bin ich am ausmisten und hab die serielle Ausgabe rausgemacht. Dann ging es nicht mehr. Warum geht es denn dann mit ? ? ? :confused: :confused:

sschultewolter:
void leerErsetzen(char* old, char* new);

Weil ich ein Zeichen durch 3 ersetze. Gut, ich hätte die anderen schieben können, aber wie gesagt, Die Routine hatte ich irgendwo herkopiert und es ging ja immer. :confused:

Dann war es sicherlich nicht C bzw C++ für µC. Wäre mir neu, dass sowas geht. Problem ist, dass eine Speicherverwaltung nur sehr schwer bis garnicht möglich ist. Der Speicher muss beim Kompilieren bereits vorhanden sein.

Warum schieben? Schreibe Zeichen für Zeichen in einen neuen String/Zeichenkette. Tritt ein Leerzeichen auf, werden 3 statt ein Zeichen eingesetzt. Du solltest nur sicherstellen, dass der Speicher dafür ausreichend groß deklariert ist.

sschultewolter:
Schreibe Zeichen für Zeichen in einen neuen String/Zeichenkette. Tritt ein Leerzeichen auf, werden 3 statt ein Zeichen eingesetzt. Du solltest nur sicherstellen, dass der Speicher dafür ausreichend groß deklariert ist.

Das macht doch die Routine, liest aus dem übergebenen Parameter und schreibt in buffer

Die Serielle Ausgabe geht natürlich in der Funktion weil der Speicher an der Stelle noch existiert. Am Ende der Funktion wird der Stackpointer verschoben und der Speicher kann dann überschrieben werden.

Warum geht es denn dann mit ?

Die Frage ist schon im Ansatz falsch.

Buffer wird auf dem Stapel angelegt.

return (buffer);

Gibt die Adresse des Buffers (auf dem Stapel) zurück.
Später werden die nächsten Funktionsaufrufe den Stapel überschreiben.
Das ist so.
Und ob die eine Funktion das sofort tut, oder die nächste später, ist egal.

Innerhalb deiner Funktion ist der Zeiger auf den String natürlich gültig.
Darum tuts dein Serial.print() IN der Funktion perfekt und zuverlässig.

Deine Funktionsrückgabe ist ein typischer, in die Wiese zeigender, Zeiger.

Serenifly:
Die Serielle Ausgabe geht natürlich in der Funktion weil der Speicher an der Stelle noch existiert. Am Ende der Funktion wird der Stackpointer verschoben und der Speicher kann dann überschrieben werden.

Das das seriell innerhalb geht, das meinte ich nicht. Sondern, wenn ich innerhalb einmal seriell ausgebe, gibt die Funktion den buffer korrekt zurück.

Aber egal, ich weiß jetzt warum und es geht ja jetzt.

gibt die Funktion den buffer korrekt zurück.

Das tut sie nicht.
Sie gibt einen Zeiger auf den Buffer zurück.
Oder ist hier “Referenz” das bessere Wort?

Egal wie man das nun nennt, die Zeile
Serial.println(leerErsetzen("Pin 5 alarm"));

Bringt dann jedenfalls wie erwartet Pin%205%20alarm als Ergebnis, obwohl das ja nicht sein dürfte. Das hat mich irritiert.

hi,

das wird Dir nur jemand erklären können, der die interna des atmel aus dem ff kennt.

ansatz:

beim schreiben mit serial.print werden vielleicht variablen auf dem stack angelegt und erhalten, bis alles rausgeschrieben wurde. weil diese variablen ja "unterhalb" des buffers liegen, würde dann der bereich des buffers nicht so schnell überschrieben und in dieser kurzen zeit kannst Du ihn dann noch nutzen.

kann stimmen, muß aber nicht....

gruß stefan

Ja, so wirds wohl sein....

Nach dem verlassen der Funktion, bleibt es dem Zufall überlassen, wann der Speicher überschrieben wird.
z.B. der nächste Timer0 Interrupt wird da gnadenlos drüber bügeln. Der hinterlässt einen recht großen "Fußabdruck" auf dem Stack.

In deinem Fall hilft ein einfaches

  [b]static [/b] char buffer[200];   //ziel

michael_x:
In deinem Fall hilft ein einfaches

  [b]static [/b] char buffer[200];   //ziel

Ja, das wäre eine Möglichkeit die gelieferte Adresse "sauber" zu halten....

Aber warum muss ich "Aua" denken, wenn ich das sehe....?

Aber warum muss ich "Aua" denken, wenn ich das sehe....?

Weil du zu allgemeingültigen Lösungen neigst. :wink:

Dafür auch meine Einschränkung "In deinem Fall".
Bei Arduino braucht man nicht "thread safe" zu programmieren, und leerErsetzen wird auch nicht rekursiv verwendet werden.

Andererseits denke ich "Aua", wenn jemand mal eben ein 200 byte Array auf den Stack legt, vermutlich ohne sich überhaupt etwas dabei zu denken :wink:

Und 7 Buchstaben "static " eintippen und das Problem ist gelöst, hat doch was, oder?

Sonst müsste man weit ausholen und was von "der Aufrufer sollte den Ergebnis-Speicher und seine Größe zur Verfügung stellen" erzählen.

hi,

irgendwie hab’ ich da auch ein aua.

static sollte doch dafür da sein, eine variable nur einer einzigen funktion zugänglich zu machen. wenn ich dann den zeiger auf die variable übergebe, umgehe ich das konzept. da wäre doch eine globale variable das richtige, meine ich.

was mir aber wirklich sorgen macht:
wenn eine statische lokale variable wie andere lokale variablen auf dem stack abgelegt wird (wo könnte man das nachlesen?), dann kann der platz “oberhalb” dieses stack-bereiches, der zum zeitpunkt des anlegens besetzt ist, nie wieder freigeben werden.
ich meine damit, daß, wenn man “über mehrere funktionen” zur funktion mit der statischen variablen springt, der platz für die lokalen variablen und die rücksprungadressen dieser funktionen nicht wieder freigegeben werden kann.

hmmm…

gruß stefan

Wenn eine statische lokale variable wie andere lokale variablen auf dem stack abgelegt wird

Wird sie nicht, sie liegt neben den globalen Variablen.

tja, in diesem fall ist auch mein obiger erklärungsversuch wertlos geworden und die frage bleibt...

Der Speicher ist aber permanent belegt. Das ist nicht so toll. Außer man sieht in der IDE vielleicht dass der Speicher weg ist. Weiß gar nicht ob das der Fall ist. Aber wenn man genug Speicher frei hat ist es auch kein Problem größere Blöcke auf dem Stack kurz zu belegen.

Allgemein werden lokale statische Variablen wie globale Variablen behandelt. Nur dass sie lokalen Scope haben.