Rückgabe eines Char-Arrays aus einer Funktion

Hallo Forum,

ich habe eine Frage zum besseren Verständnis.

Gestolpert bin ich über diesen Konstrukt:
char *s = strcat(strdup("irgendwas"), "nochwas");
Ich war der Meinung (eher Hoffnung) das die Funktion strcat die Größe des Puffers anpasst. :face_with_raised_eyebrow: Macht sie aber nicht. Also habe ich mir eine Funktion dafür gebastelt.

char *strAnhaengen(const char *s1, const char *s2){
  int l1 = strlen(s1);
  int l2 = strlen(s2);
  char s[l1 + l2 + 1] = {'\0'};
  strcat(s, s1);
  strcat(s, s2);
  return s;
}

Ich erhalte folgende Warnung:

warning: address of local variable 's' returned

Klar der Speicherplatz von s wird nach dem verlassen der Funktion freigegeben. Also habe ich s noch einmal kopiert.
return strdup(s);
Der Compiler meckert nun nicht mehr.
Aber ist das der richtige Weg?
Wie würdet ihr das lösen?
Kann man den Speicherbereich von s1 vergrößern (Übergabe dann ohne const)?

Grüße Martin

C, dann größeren Buffer verwenden,
C++ die String Klasse.

In C++ ist das Zauberwort für sowas RAII

1 Like

Danke.

Du meinst s1 so groß machen das man auf alle Eventualitäten vorbereitet ist? Das Array s1 kommt aus einer Funktion auf die ich keinen Einfluss habe und s1 vergrößern bedeutet ja neues Array anlegen und kopieren.

Wird davon nicht immer abgeraten wegen Ram Verbrauch und so?

Darum habe ich bis jetzt immer einen großen Bogen gemacht. Muss ich wohl mal ändern.

Grüße Martin

Ja ja... "immer"
Wenn man RAM braucht, dann braucht man RAM.
String implementiert RAII!

Tut das nicht, darum baut man sich damit schnell Memory Leaks.

Man/Du hast die Wahl.

Was erscheint dir beherrschbarer?

1 Like

Danke dir.

Strings sind natürlich einfacher zu handhaben und in der aktuellen Version des Programms das auf einem ESP8266 läuft verwende ich auch Stringobjekte. Dann habe ich ein wenig in Foren gestöbert und in einem in der ersten Beiträge wird meistens vor Strings gewarnt.

Da hätte ich noch eine Frage. Oftmals verlangen Funktionen bzw. Methoden von Bibliotheken einen const char* Parameter. Baue ich den String mit Stringobjekten zusammen und übergebe das Ergebnis dann per .c_str() wird das Stringobjekt dann aus dem Ram entfernt wenn es ungültig wird oder erst wenn auch die übergebene Zeichenkette nicht mehr gebraucht wird? Oder anders gefragt, zeigt der übergebene Zeiger auf Speicher im Stringobjekt?

Grüße Martin

@martindi
ich würde ja eher mal zur Wurzel des "Problems" gehen und kritisch hinterfragen, weswegen es überhaupt eine Konkatenierung von c-strings (oder char Arrays?) braucht.

strings/Strings werden ja oft in Verbindung mit Ausgaben via print verwendet
In so einem Fall bietet sich an, man nutzt die Fähigkeiten der Stream Klasse und streamt die Werte zum Ziel ohne Umwege von einem separaten Buffer.

1 Like

Aha. Da hat mich dann interessiert was die Abkürzung "RAII" bedeutet.
Habe diese kurze Erklärung gefunden:
Die Abkürzung RAII steht für Resource Acquisition Is Initialization (auf Deutsch: Ressourcenbelegung ist Initialisierung) und bezeichnet ein wichtiges Programmieridiom in C++, das zur Verwaltung von Ressourcen wie Speicher, Datei-Handles oder Mutexen verwendet wird. Es bindet die Lebensdauer einer Ressource an die Lebensdauer eines Objekts, indem die Ressource beim Konstruktoraufruf des Objekts belegt und beim Destruktoraufruf wieder freigegeben wird 1 5.

Funktionsweise von RAII

  • Ressourcenbelegung: Beim Erstellen eines Objekts wird die benötigte Ressource automatisch durch den Konstruktor initialisiert. Dies kann beispielsweise das Öffnen einer Datei, das Sperren eines Mutexes oder das Allozieren von Speicher sein 1 5.
  • Ressourcenfreigabe: Sobald das Objekt aus dem Gültigkeitsbereich verschwindet (z. B. am Ende eines Blocks oder bei einer Ausnahme), wird der Destruktor des Objekts aufgerufen, der die Ressource automatisch freigibt 1 5.

RAII und C++-Zeichenketten

In C++ ist der Typ std::string ein Beispiel für ein RAII-Objekt. Die Klasse kümmert sich automatisch um die Speicherverwaltung der Zeichenkette:

  • Der Konstruktor von std::string initialisiert die Zeichenkette (z. B. als leere Zeichenkette bei der Standardinitialisierung).
  • Der Destruktor von std::string gibt den Speicher frei, wenn das Objekt zerstört wird, wodurch Speicherlecks vermieden werden 2 4.

Vorteile von RAII

  • Ausnahmefestigkeit: Ressourcen werden auch bei einem frühzeitigen Verlassen des Gültigkeitsbereichs durch eine Ausnahme sicher freigegeben.
  • Einfachere Codeverwaltung: Entwickler müssen sich nicht explizit um das Freigeben von Ressourcen kümmern, da dies automatisch geschieht.
  • Fehlervermeidung: RAII reduziert die Gefahr von Speicherlecks oder nicht freigegebenen Ressourcen erheblich 1 5.

Reduziert die Gefahr erheblich. Ich interpretiere: bringt die Gefahr aber nicht auf null.
Ja und jetzt wäre - wie @noiasca auch schon angemerkt hat- interessant zu wissen was die Gesamtanwendung ist. Wenn ein ESP8266 verwendet wird, der hat ja vergleichsweise viel RAM. Auch nicht (fast) endlos aber doch deutlich mehr als ein A.Uno. Da könnte man eine vergleichsweise großzügige Maximalgröße festlegen in die dann garantiert alles wirklich auftretende reinpasst.

1 Like

Hallo noiasca,

das Programm liest Daten von einer seriellen Schnittstelle (BKW-Speicher) und stellt sie per Web-API und MQTT zur weiteren Verarbeitung zur Verfügung. Die nötigen Einstellungen können per Weboberfläche eingegeben und per LittleFs gespeichert werden.
Zur Datenübergabe wird eine Json-Datei erstellt.
Verwendete Bibliotheken sind LittleFS, ESP8266WiFi, SP8266WebServer, PubSubClient und time. Fast überall werden Zeichenkette verwendet.

Das Projekt auf Github, wer Lust hat kann ja etwas kritisieren. :grinning_face:

Grüße Martin

Danke für Erklärung. Ich kannte zwar den Begriff noch nicht praktiziere das aber schon sehr lange. Angefangen bei Pascal unter DOS über Delphi unter Windows, Java unter Linux (da spielen Destructoren aber keine große Rolle, erledigt Java selbst) und nun recht neu C++ für Mikrokontroller.

Man muss sparen wo man kann, koste es was es wolle. :wink:

Grüße Martin

Wenn man sich darüber Gedanken macht, kann man mit Strings gut arbeiten.

Solange man es lokal hält, ist es nicht schlimm Strings zu verwenden, denn der Speicher wird dann schnell wieder frei gegeben und eine Fragmentierung kommt kaum zu Stande. Bei globale Strings ist es schwierig, da immer unterschiedlich langer Speicher gebraucht wird. Dann entstehen Lücken im Speicher. Oder der String wird kürzer, gefolgt wird im Freien Bereich etwas anderes gespeichert, String wird wieder genauso lang wie vorher, passt dadurch nicht mehr an die Stelle im Speicher, ein neuer Bereich muss reserviert werden. Das alles fragmentiert den Speicher, und irgendwann ist kein Platz mehr für dein String und das Programm stürzt ab.

Um so weniger Arbeitsspeicher dein μC hat, um so eher tritt das Problem auf.

1 Like

Das glaub ich dir gern.

Es geht nicht darum etwas zu kritisieren sondern zu unterstützen ein XY-Problem zu vermeiden.

Wo wird in deinem Projekt etwas konkatiniert und warum?

Für positive Kritik, Hilfe bin ich immer offen.

z.B.

//  if((einst.mqttTp + "/Befehl").equals(topic)){                      // Wenn einst.mqttTp ein Stringobjekt ist
//  if(strcmp(topic, strcat(strdup(einst.mqttTp), "/Befehl")) == 0){   // funktioniert nicht
  if(strcmp(topic, strAnhaengen(einst.mqttTp, "/Befehl")) == 0){     // strAnhaengen siehe erster Beitrag

Im ersten Beitrag ging es mir besonders um die Funktion strAnhaengen.

oder beim erstellen der Json-Datei

// Variante mit Stringobjekt, json ist ein leerer String (String json;)
    json = "{\"Spannung\":" + (String)spannung;
    json += ",\"Ladezustand\":" + (String)soc;
    json += ",\"StromAkku\":" + (String)stromakku;
    json += ",\"Typ\":" + (String)typ;
    if(typ == 2){
        json += ",\"StromPV\":" + (String)strompv;
        json += ",\"Temperatur\":" + (String)temperatur;
    }else{
        json += ",\"StromPV\":0,\"Temperatur\":0";
    }
    json += ",\"Datum\":" + ("\"" + datum + "\"");
    json += ",\"Zeit\":" + ("\"" + zeit + "\"");
    String s = (laden)? "ein": "aus";
    json += ",\"Laden\":" + ("\"" + s + "\"");
    s = (entladen)? "ein": "aus";
    json += ",\"Entladen\":" + ("\"" + s + "\"");
    json += "}";
// Variante char*, json ist ein Array mit 200 Byte (char json[200] = {'\0'};)
    sprintf(json, "{\"Spannung\":%.2f,\"Ladezustand\":%d,\"StromAkku\":%.2f,\"Typ\":%d",
        spannung, soc, stromakku, typ);
    char s[120] = {'\0'};
    if(typ == 2){
        sprintf(s, ",\"StromPV\":%.2f,\"Temperatur\":%d", strompv, temperatur);
    }else{
        strcat(s, ",\"StromPV\":0,\"Temperatur\":0");
    }
    strcat(json, s);
    sprintf(s, ",\"Datum\":%s,\"Zeit\":%s,\"Laden\":%s,\"Entladen\":%s",
        datum, zeit, (laden)? "\"ein\"": "\"aus\"", (entladen)? "\"ein\"": "\"aus\"");
    strcat(json, s);
    strcat(json, "}");

Grüße Martin

Für ungenutzten Speicher gibts kein Geld zurück.

Großer Meister!
Natürlich hilft ein Regenschirm nur gegen nass werden.
Nicht gegen Filzläuse.

Vielleicht möchtest du die anderen Memory Leak Möglichkeiten ja auch stopfen.
Dann sei dir zu "C++ smart Pointer" geraten.

deine Variable json ist bereits ein String Objekt.
An deiner Stelle würde ich (wenn man das JSON so zusammenstellen will) daher nicht die spannung auf String casten und dann hinzufügen sondern das in zwei separaten Zeilen schreiben und den cast einmal von der String Klasse erledigen lassen:

    json = "{\"Spannung\":" ;
    json += spannung;

Trotzdem würde ich auch auf einem ESP dem String json mit reserve() eine maximale Größe vorgeben.

Der eigentliche Punkt ist:
du hast beispielsweise eine Variable spannung
Nach genJson hast du nun in einem String json auch die Base10 Representation des Wertes in deinem JSON. Wozu brauchst du eine doppelte Speicherung deiner Variablen?
Genau hier könntest ansetzen und eben deine Werte direkt in ein JSON ausgeben lassen - und auch das JSON dann dort hinsenden lassen wo du es brauchst - nicht redundant zwischenspeichern!

Wo du den zweiten Codeschnippsel rauskopiert hast weis ich nicht. Mir kommt aber der Mix von sprintf und char arrays da schon eingenartig vor.

Wenn du an mehreren Stellen mit JSON arbeitest, warum nutzt du dann nicht die ArduinoJSON Library? dann könntest deine verschiedenen Logiken ein wenig zusammenräumen.

1 Like

Danke für deine Tipps.

Es ist halt gut wenn jemand anderes sich den Code ansieht, selbst stellt man so etwas nicht in Frage. Ich löse immer eine Aufgabe nach der Anderen, Telegramm einlesen, Daten speichern und dann Json String erstellen. Danke für den Hinweis.

Das war mein Versuch ohne Stringobjekte auszukommen. Ist nun hinfällig.

Eine Library, die ja alles mögliche abdecken muss, erschien mir zu mächtig nur um zwei Json-Strings zu erstellen. Ich kann sie mir ja mal ansehen.

Grüße Martin

Sehr vernünftig. Die ist effizient und einfach zu handhaben.
Probier mal den Assistenten auf Benoits Seite aus... dann siehst Du es selbst.

2 Likes

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