Alternative zu Strings

Serenifly:
In C sind Strings Null-terminierte char Arrays. Die kann man aber z.B. nicht direkt konkatenieren, da Arrays wiederum nur Zeiger auf das erste Element sind. Es gibt für den Umgang damit aber zig Jahrzehnte-alte Standardfunktionen fürs Zusammenfügen, Splitten, Suchen oder Konvertieren (nach Zahlen und zurück).

Das weiß ich, habe schon einiges mit C Strings gemacht bin aber durch Java etwas gemütlich geworden (da nutze ich aber StringBuilder zum zusammenfügen). Deshalb war mir die String Klasse von Arduino willkommen. Jetzt werd ich wohl aber wieder mit den C String Befehlen arbeiten, die ich ja auch schon teilweise verwendet habe zum Zerlegen oder Umwandeln. Meine Frage war ja nur, ob dieser Array-Speicherbereich dann sauber immer wieder verwendet wird, wenn ich die chars da durch die C String Befehle reinpacke (die machen ja alles byte-weise). Ich werd mal alles mit einem Char Buffer umbauen.

LeRoi:
Das weiß ich, habe schon einiges mit C Strings gemacht bin aber durch Java etwas gemütlich geworden (da nutze ich aber StringBuilder zum zusammenfügen).

Und Java nutzt Du auf Geräten von mindestens dem Typ Smartphone mit 256 MB RAM oder mehr, also mindestens ca. ca. hundertachtundzwanzigtausend mal so viel RAM wie ein UNO hat. Gar nicht zu reden von PCs mit Gigabytes an RAM, also x-millionenfachem an RAM wie ein UNO board.

LeRoi:
Deshalb war mir die String Klasse von Arduino willkommen. Jetzt werd ich wohl aber wieder mit den C String Befehlen arbeiten, die ich ja auch schon teilweise verwendet habe zum Zerlegen oder Umwandeln. Meine Frage war ja nur, ob dieser Array-Speicherbereich dann sauber immer wieder verwendet wird, wenn ich die chars da durch die C String Befehle reinpacke (die machen ja alles byte-weise). Ich werd mal alles mit einem Char Buffer umbauen.

Erstmal musst Du Dich von dem Gedanken frei machen "alle ankommenden Zeichen in eine Variable" packen zu wollen. Bei extrem wenig RAM brauchst Du ganz andere Programmiertechniken.

Mit C-Strings definierst Du Dir Deine Zeichenkettenpuffer üblicherweise einmalig und global in der maximal benötigten Größe (bei einem GPS-Programm, das Zeichen per NMEA-Datenzeilen empängt oder einem seriellen Kommandointerpreter beispielsweise für einen String mit maximal 80 Zeichen (eine Zeile) und dieser eine Zeichenpuffer kann in Programm immer wiederverwendet werden. Allenfalls legst Du kleine Zeichenkettenvariablen noch dynamisch auf dem Stack an, indem Du sie als lokale Variable innerhalb einer Funktion anlegst, z.B.

 void printFormattedTime (int hour, int minute, int second)
 {
  char timeStr[9];
  snprintf(timeStr, sizeof(timeStr), "%02d:%02d:%02d", hour,minute,second)
  Serial.println(timeStr);
 } // hier ist zur Laufzeit die Funktion und damit die Gültigkeit der Variablen timeStr beendet, und die auf dem Strack zu Funktionsbeginn reservierten 9 Bytes werden vollständig und ohne irgendwelche Fragmentierung wieder dem Stack zugeschlagen, bis zum nächsten Funktionsaufruf einer Funktion.

Wenn Du extrem wenig RAM hast, darfst Du nicht "alles", sondern nur das "absolut notwendige" gleichzeitig im RAM halten. So gesehen ist schon mein Funktionsbeispiel völlig überzogen im RAM-Verbrau ch, weil man dasselbe mit etwas anderen Befehlen auch ohne diesen 9-Zeichen-Ausgabepuffer erreichen könnte. Wobei allerdings meistens gilt: Um mehr RAM zu sparen, mußt Du auch mehr zusätzlichen Programmcode schreiben, d.h. die Größe des C-Codes in der INO-Datei wird unter ungünstigenUmständen größer als bei Anwendung von Programmiertechniken, mit denen RAM verschwendet wird.

Meine Frage war ja nur, ob dieser Array-Speicherbereich dann sauber immer wieder verwendet wird

Das liegt ganz an dir.
Im Gegensatz zu Java gibt es in C/C++ erstmal kein automatisches Speichermanagement oder Müllabfuhr (garbage collection). Wenn dunewverwendest, musst du auchdeletemachen, oder du machst new einmal nach Reset und weisst, was du tust. Oder du gönnst dir den Luxus von String-Objekten, die das -inzwischen einigermaßen ordentlich- selbst erledigen. (Kriegst du aber hier im Forum immer "Tu's Nicht, Strings sind Böse" - Hinweise :wink: )

Die "Arduino-Sprache" war schon immer so gedacht, dass sie Java ähnlich sieht, um es dadurch Nicht-Programmierern etwas einfacher zu machen. Daher gab es schon immer String Objekte (anfangs wirklich schrottig). Kostet natürlich etwas an Performance und Speicher.
Einfaches noch einfacher erscheinen zu lassen birgt leider auch das Risiko, dass es umso komplizierter wird, wenn dies an seine Grenzen stößt.

Und - für mich - besteht der Reiz ja grade darin, hübsche Sachen mit wenig Ressourcen zu machen.
Back to the Roots


Puffer mit variablem Inhalt liegen übrigens nie im Flash, da brauchst du dir keine Sorgen zu machen.
Feste Texte sollten zur Speicherplatz-Optimierung jedoch bevorzugt aus dem Flash gelesen werden.

jurs:
Und Java nutzt Du auf Geräten von mindestens dem Typ Smartphone mit 256 MB RAM oder mehr, also mindestens ca. ca. hundertachtundzwanzigtausend mal so viel RAM wie ein UNO hat.

Das geht auch mit weit weniger. Es gibt Java VMs für ARM Prozessoren mit ca. 100kB RAM die auch noch vertretbar schnell sind.

/off topic

Wo du bei Arrays halt aufpassen musst ist dass du nicht über das Ende schreibst. Du hast volle Kontrolle darüber wiegenau der Speicher verwendet wird. Mit allen Vorteilen und Nachteilen.

Wobei String Objekte auch nicht sooo schlimm sind wenn man sie korrekt verwendet. Wozu die meisten Anfänger aber nicht in der Lage sind, weil sie nicht verstehen was dabei gemacht wird. Also so programmieren dass man nicht ständig Objekte kopiert und reserve() verwenden statt ständig den Speicher anzupassen.

michael_x:
Das liegt ganz an dir.
Im Gegensatz zu Java gibt es in C/C++ erstmal kein automatisches Speichermanagement oder Müllabfuhr (garbage collection). Wenn dunewverwendest, musst du auchdeletemachen, oder du machst new einmal nach Reset und weisst, was du tust. Oder du gönnst dir den Luxus von String-Objekten, die das -inzwischen einigermaßen ordentlich- selbst erledigen. (Kriegst du aber hier im Forum immer "Tu's Nicht, Strings sind Böse" - Hinweise :wink: )

Die "Arduino-Sprache" war schon immer so gedacht, dass sie Java ähnlich sieht, um es dadurch Nicht-Programmierern etwas einfacher zu machen. Daher gab es schon immer String Objekte (anfangs wirklich schrottig). Kostet natürlich etwas an Performance und Speicher.
Einfaches noch einfacher erscheinen zu lassen birgt leider auch das Risiko, dass es umso komplizierter wird, wenn dies an seine Grenzen stößt.

Und - für mich - besteht der Reiz ja grade darin, hübsche Sachen mit wenig Ressourcen zu machen.
Back to the Roots


Puffer mit variablem Inhalt liegen übrigens nie im Flash, da brauchst du dir keine Sorgen zu machen.
Feste Texte sollten zur Speicherplatz-Optimierung jedoch bevorzugt aus dem Flash gelesen werden.

Den Reiz mit wenig Speicher und geringer Taktrate viel zu machen, kann ich nachvollziehen. Ich hab mit Microchips Controllern mit 1kB Flash angefangen (wenigstens gleich nen 20Mhz Quarz angelegt) und viel mit Assembler gecodet, was mir am meisten Spaß macht, da es für mich wie eine Rätselaufgabe ist, die man besonders elegant und clever lösen möchte. Auch wenn sich das kaum noch lohnt, ist es immer wieder spannend, die Machinenbefehle kennenzulernen, zu verwenden und neue Möglichkeiten zu entdecken.

Danke für eure doch recht ausführlichen Antworten. Ich werde jetzt feste Char Arrays (read und write Buffer) anlegen (max nötige Größe kann ich direkt am Anfang berechnen) und mich wieder den C String Befehlen zuwenden. Das fühlt sich dann einfach besser an.

Hallo,

dreht ihr euch nicht irgendwie im Kreis?
Die einen sagen Strings sind böse. Wüßte nicht warum.
Die anderen sagen char arrays wären besser.
Derweile wissen wir doch das Strings auch nur char arrays sind.
Also warum werden diese gegeneinander gestellt?
Ich verstehe die Diskussion nicht.
Im Grunde geht's doch nur darum so wenig und nur so groß wie nötig einen globalen String zu haben und den Rest mit kleinen lokalen Strings zu erledigen.

Derweile wissen wir doch das Strings auch nur char arrays sind.

Ich weiss, dass sie ein char array zurückliefern können, aber etwas ganz anderes sind.

Ich spare es mir hier aber, die Unterschiede aufzuzählen.

Speicher-Effizienz ist nicht das einzige Problem mit der String Klasse. Sie ist auch in ihrer Funktionalität stark eingeschränkt. An Konvertierungs- und Formatierungs -Funktionen mangelt es fast völlig. Also Dinge wie die Behandlung von Hex-Zahlen oder eine genau einstellbare Formatierung von Float Werten.
split() wurde entfernt, also ist ein Alptraum per Hand Teil-Strings abzutrennen. Wobei wie dabei wieder bei der Speicher Sache sind, da man dauernd neue Objekte bekommt statt Strings einfach in situ bearbeiten zu können.

Hallo,

okay, ich hatte das in meinem Buch nachgelesen das Strings "nur" char Arrays sind. Scheinbar falsch ausgedrückt oder zu kurz erklärt. Ist ein Anfängerbuch. Mittlerweile, auf Grund des Kommentars weis ich es nun etwas besser.
Strings sind Zeiger auf eine Zeichenkette, die können aus char arrays bestehen.
Und char array ist eben ein array bestehend aus Einzelzeichen.
Und string ist auch ein Datentyp.
Soweit so gut. :slight_smile:

Mit der Einschränkung meinst du die vorhandenen Befehle auf den Arduino?
Denn Stringfunktionen gibts eigentlich im Überfluss.
Man muß nur wissen welche man davon benötigt, dass ist meistens das größte Problem.
Wegen dem zerteilen. Da kam ich zufällig drauf. String Remove sollte dafür passen.

Doc_Arduino:
Strings sind Zeiger auf eine Zeichenkette, die können aus char arrays bestehen.

Nein. Strings sind Objekte die intern ein char Array verwalten. Man kann sogar mit c_str() inzwischen direkten Lese-Zugriff auf das interne Array bekommen. Das ist eine Art Wrapper Klasse die einfache Methoden enthält um das Array zu manipulieren.

Das Array ist aber immer nur so groß wie es gerade sein muss. Wenn man mit + ein Zeichen einfügt wird jedesmal realloc() gemacht. Das kann man mit reserve() umgehen indem man am Anfang einmal Speicher anlegt. Macht aber kaum jemand.

Und da es Objekte sind werden z.B. bei der einfachen per Value Übergabe oder Zuweisungen Kopien erstellt. Die Ausnahme sind Strings als Rückgabewerte von Funktionen. Das kann der Compiler schön wegoptimieren.
Da muss man wirklich aufpassen und Anfänger haben keine Ahnung was da im Hintergrund eigentlich abläuft.

Ich glaube man kann das so einfach niemandem begreiflich machen, man muss es erlebt haben.

Solange man nur innerhalb von loop irgendwelche Kleinigkeiten ausdruckt

  • viel mehr machen die teilweise haarsträubenden Beispiele nicht - sind Strings kein Problem.
    Ebenso das Fehlen von F(), was konsequent um jede konstante Zeichenkette gehört (alternativ PSTR() oder ähnliches).

Kombiniert man WiFi (non ESP), SD Karte, Wire, RTC und Serial, dann wird es auf einem 328 sehr sehr eng,
auf einmal ist mehr als die Hälte oder noch mehr des Speichers 'verbuffert'.

Dann lernt man die Tücke der Strings kennen, die einfach nicht mehr funktionieren (können).
Wirkliche Fehlerbehandung wie Exceptions gibt es nicht, ich weiss nicht einmal, ob es möglich ist,
nachzusehen, ob eine String Funktion aus Speichermangel fehlschlug.

Die Fehlersuche gestaltet sich in so einem Umfeld... sagen wir mal 'interessant'.

Selbst wenn die Arduino String Klasse besser geworden ist, wird dies immer mehr Ressourcen verbrauchen wie ein nullterminierter Char Array.
Geht ja nicht anders, jedes String Objekt braucht neben dem Char Array noch Variablen zur Verwaltung etc. Und das kostet. (unnötig)

Für die Char Array's bietet der Compiler "mächtige" Funktionen, ich vermisse dort nichts, ist alles da was man braucht. :slight_smile:

rudirabbit:
Selbst wenn die Arduino String Klasse besser geworden ist, wird dies immer mehr Ressourcen verbrauchen wie ein nullterminierter Char Array.
Geht ja nicht anders, jedes String Objekt braucht neben dem Char Array noch Variablen zur Verwaltung etc. Und das kostet. (unnötig)

Für die Char Array's bietet der Compiler "mächtige" Funktionen, ich vermisse dort nichts, ist alles da was man braucht. :slight_smile:

Ein nicht zu unterschätzender Vorteil von der String-Klasse mit dessen leistungsfähigen Befehlen ist aber die bessere Lesbarkeit, weshalb z.B. Java auch viel einfacher nachzuvollziehen ist als ein C-Code insbesondere wenn sich der Coder besonders clever mit Pointer Tricks vorkommt. Macht zwar Spaß und stimmt einen zufrieden ist aber trotzdem heutzutage nicht wirklich gut und zeitgemäß.
Noch was Anderes: <Klugscheißer-Modus an> Die Mehrzahl von Array ist Arrays, ich kann das nahzu immer falsch verwendete Apostroph besonders in deutschen Foren einfach nicht mehr sehen, es ist wie ein Dorn im Auge. Sorry, möchte Keinem auf den Keks gehen, musste nur mal raus. :slight_smile:

LeRoi:
Ein nicht zu unterschätzender Vorteil von der String-Klasse mit dessen leistungsfähigen Befehlen ist aber die bessere Lesbarkeit

Mir ist Stabilität wichtiger als ein Java Aussehen.

Und 'Coder' halte ich für einen völlig unnötigen Anglizismus.
Verbinde ich persönlich auch eher mit Script-Kids als mit Programmierern.

Hallo,

und ich dachte ich hätte es halbwegs begriffen. Solange ich die internas nicht wirklich benötige, komme ich damit klar. Ansonsten muß ich euch fragen. :slight_smile:

So, hab jetzt alle Strings gegen char Arrays ausgetauscht und verwende nun zwei charBuffer für read und write. Beschrieben wird der writeBuffer mit sprintf, mit dem Rückgabewert kann ich wunderbar den bufferPointer verschieben, um anzuhängen. Bei strcat würden mir die Umwandlungen und Formatierungen fehlen. Relativ übersichtlich ist das ganze auch, bis auf ein wenig Pointer Arithmetik hier und da bei den str... Befehlen.
Ein Frage hätte ich noch. Ich habe folgende Code-Zeilen verteilt in meinem Sketch:
Ich habe ein Array aus Zeigern, die auf dynamische Char Arrays zugreifen.
Ich möchte die Länge der Client-Namen nicht vordefinieren. Kann man das so machen wie im folgenden Code? Wie kann ich den Speicher am besten wieder freigeben, wenn ein Name nicht mehr gebraucht wird?

char* clientNameList[MAX_CLIENT_NUMBER];
...
part = strtok(NULL, MESSAGE_SEPARATORS);
if (part != NULL) {
clientNameList[clientPosition] = new char[strlen(part) + 1];
strcpy(clientNameList[clientPosition], part);
}
else {
clientNameList[clientPosition] = "";
}

...
// clientName wieder 'freigeben'
clientNameList[clientPosition] = NULL;

Zunewgehört delete
Formal sogar delete [ ] , wenn mit new ein Array erzeugt wurde.

Viel Spass beim Fehler abprüfen. Nach new musst du prüfen, ob du überhaupt was gekriegt hast und nicht einfach strcpy(0, someDataToKill) ausführst. Exceptions gibt es nicht.
Kann auch sein, dass du eine Adresse kriegst, deren Inhalt aber später durch Stack-Daten überschrieben wird. Dagegen hilft nur, new nicht zu verwenden und mit dem Stack sparsam umzugehen.

Statt Strings selber new zu verwenden, ist nicht die Lösung zu der wir dich hier prügeln wollen. :wink:

Warum kannst du MAX_CLIENT_NUMBER hart begrenzen, aber nicht den Platz, der für alle Namen insgesamt zur Verfügung steht?

michael_x:
Zunewgehört delete
Formal sogar delete [ ] , wenn mit new ein Array erzeugt wurde.

Viel Spass beim Fehler abprüfen. Nach new musst du prüfen, ob du überhaupt was gekriegt hast und nicht einfach strcpy(0, someDataToKill) ausführst. Exceptions gibt es nicht.
Kann auch sein, dass du eine Adresse kriegst, deren Inhalt aber später durch Stack-Daten überschrieben wird. Dagegen hilft nur, new nicht zu verwenden und mit dem Stack sparsam umzugehen.

Statt Strings selber new zu verwenden, ist nicht die Lösung zu der wir dich hier prügeln wollen. :wink:

Warum kannst du MAX_CLIENT_NUMBER hart begrenzen, aber nicht den Platz, der für alle Namen insgesamt zur Verfügung steht?

Ich benutze sehr wenig vom Stack (wenige Varieblen in den Funktionen), da ja die 2 Buffer global fest sind und ich sonst nur noch ein paar Hilfsbyte gobal hab, die ich mehrmals verwende. Sonst müsste ich jede Kleinigkeit in Funktionen packen und die Werte übergeben. Im Endeffekt wird sowieso immer in die Buffer direkt geschrieben, sehe da keinen Sinn in Funktionen erst einen Hilfsbuffer anzulegen. Und ob ich nun in der loop() 'static' für einige byte-Variablen oder sie global mache, ist für den Fall egal, da ich alles in passender Reihenfolge mache, auch wenn ich sonst globals vermeide. So sehe ich auch schon beim Kompilieren wieviel vom Heap belegt ist und wieviel für den Stack bleibt.
Was soll ich denn anstelle von new verwenden? Ich nutze das ja nur in diesem speziellen Fall.
Wenn ich vorher alles fest dimensioniere, bräuchte ich eine Menge Platz. Es könnten viele Clients hinzukommen und jeder mögliche Client belegt schonmal festen Platz für den einen variablen Namen. Die Menge muss ich schon hart begrenzen, irgendwo muss man ja bei einem Microkontroller aufhören. Ich hab gelesen, man soll nur soviel reservieren, wie auch dann gebraucht wird.

Das Problem ist: global und static erkennt der Compiler leicht und kann meckern.
Lokale Variable auf dem Stack hängen von der Aufruf-Schachtelungstiefe zur Laufzeit ab und mit new belegter Speicher auf dem Heap wird erst recht erst zur Laufzeit problematisch.

Ich hab gelesen, man soll nur soviel reservieren, wie auch dann gebraucht wird.

Das ist der übliche Programmierer-Ansatz.

Andersrum hast du evtl. etwas "verschenkten Platz", aber es ist sicherer:
Man darf nur soviel verwenden, wie vorhanden und schon fest reserviert ist.

Da du schreibst, es sei bei dir "immer alles gut", ist es eine theoretische Diskussion undnew oderString kommen nie an die Grenzen bei dir. Ausserdem ist es evtl. viel einfacher auf einen reichlich dimensionierten Ausgabe-Puffer ganz zu verzichten.

Wenn man wirklich geizt, kriegt man einiges in 2kB RAM. Vor allem, wenn man keine SD-Karte verwendet.

Die Alternative zunewoder festem Speicher wäre ein Speichermanagement-System in einem reservierten Teil der Globalen/statischen Variablen. Das könnte (theoretisch) entweder etwas generelles sein oder speziell für deine Zwecke mit weniger Code gebautes. Eine weitere Alternative wäre eine integrierte Fehlerbehandlung, die das generelle Problem von Microcontrollern, dass kein Schwein guckt und erst recht nicht helfen kann, akzeptabel löst.
Besser alsnewkann man nur in speziellen Fällen werden, die ich hier allerdings nicht kenne:

  • Warum kommst du mit einer festen MAX_CLIENT_NUMBER aus (und wie groß ist die)?
  • Wann wird irgend etwas wieder frei und kann für etwas anderes mit anderen Anforderungen (?) verwendet werden?
  • Was soll/darf im Problemfall passieren? Bringt es was, deine ClientName[] Texte abzuschneiden oder ist eine Fehler-Reaktion sinnvoller?