Forum: Mikrocontroller und Digitale Elektronik Arduino und die String Klasse


von Christian J. (Gast)


Lesenswert?

Moin,

nun sie ist da und ich benutze sie wegen des hohen Komforts der 
Stringverwaltung mit Klassen Methoden. Dass man ohne kann weiss ich, 
möchte ich aber nicht (mehr) oer nur wenn ich muss :-) C++ ist eine 
schöne Ergänzung beim Arduino, besonders die Stream Fähigkeit mit 
virtuellen Klassen hat es mir angetan.

NMEA Strings rauschen jede Sekunde über eine SoftUart rein und werden 
dank der guten Lib von Stoffregen auch genau bis zum CR+LF gelesen.

Nun will ich sie aber buffern in einem 10er Array aber dazu muss ich 
einiges wissen, bevor ich herum probiere.

1
void myfunc (String *input) {
2
   static String buffer[10];
3
   .....
4
   jeden input nach buffer kopieren
5
   usw.

brauche ich, da ich eingehende Strings buffern will in einem 
rollierenden Buffer, der dann alle 10s mal weggeschrieben wird. Muss 
sein, geht nicht anders. openlog den Strom am Geräteschalter klauen im 
sekündlichen Schreibvorgang zerschießt sehr oft die FAT, daher muss ich 
mehr Zeit zwischen den Schreibvorgängen haben.

Die Frage ist nur, wie verwaltet Arduino das? Die Länge der Strings ist 
nicht definiert, sie können maximal 80 Zeichen sein aber auch kürzer. 
Bläht sich das vielleicht sogar immer weiter auf, wenn ich das Feld 
static deklariere und es auf dem Stack angelegt wird? Vielleicht lieber 
global?

Oder wäre es hier sinnvoller die Strings vorher in einen Array zu 
kopieren und dann eben per Hand zu senden? Müssen natürlich 0 terminiert 
sein, das sind sie aber nicht von Haus aus. Nur CR+LF.

von Einer K. (Gast)


Lesenswert?

Die String Klasse hantiert mit Heap rum.
Das kann zur Speicherfragmentierung führen.
Abhilfe: String::reserve(...)
Ob global oder static ist hier völlig wurscht.
Hat nur Auswirkungen auf die Sichtbarkeit, nicht auf die Existenz.

Christian J. schrieb:
> wenn ich das Feld
> static deklariere und es auf dem Stack angelegt wird?
Statische Sachen liegen nicht auf dem Stack.

Beitrag #6556771 wurde von einem Moderator gelöscht.
von Christian J. (Gast)


Lesenswert?

Fragezeichen schrieb im Beitrag #6556771:
> Arduino Fanboy D. schrieb:
>> hantiert mit Heap
>
> Was genau soll in diesem Zusammenhang ein Heap sein? Meinst Du einen
> Header?

Heap! Gibts schon seit ca 40 Jahren= Halde! In pascal, in C usw. 
Dynamisch allozierbarer Speicher. Muss aber im Linker definiert sein wie 
gross und wo der liegt.

1
 p = malloc(sizeof(int));
2
   if(p != NULL) {
3
      *p=99;
4
      printf("Allokation erfolgreich ... \n");
5
   }

von Stefan F. (Gast)


Lesenswert?

Fragezeichen schrieb im Beitrag #6556771:
> Was genau soll in diesem Zusammenhang ein Heap sein?

https://www.mikrocontroller.net/articles/Heap-Fragmentierung

Beitrag #6556806 wurde von einem Moderator gelöscht.
Beitrag #6556807 wurde von einem Moderator gelöscht.
von Michael D. (nospam2000)


Lesenswert?

Fragezeichen schrieb im Beitrag #6556807:
> Leider verstehe ich das nicht :-(

Wenn man nicht weiß was heap ist und was Fragmentierung ist, sollte die 
Finger weglassen von Klassen die dies implizit verwenden.

Wenn man es doch verwendet sollte man sich das Wissen entweder aneignen 
oder eben damit leben, dass man nach einiger Zeit keine neuen Objekte 
(z.B. Strings) mehr allokieren kann und der Arduino einfach neu startet 
oder stehenbleibt.

So langsam fange ich an die Arduino Hasser zu verstehen, dabei ist C++ 
auf Mikrocontrollern wirklich klasse wenn man die Finger von dynamischer 
Speicherverwaltung weglässt.

 Michael

von Stefan F. (Gast)


Lesenswert?

Der Stack wird für temporäre Daten von Funktionen verwendet. Um den 
brauchst du dir keine Sorgen machen, da er am Ende jeder Funktion 
abgeräumt wird.

Auf dem Heap liegen Daten, die länger leben.
1
#include whatever
2
3
int a;
4
5
void doSomething()
6
{
7
   int b;
8
   static int c;
9
   char* d = new char[100];
10
}

a, c und die 100 Zeichen auf die d zeigt, liegen auf dem Heap.
b und d (der Zeiger, nicht die Daten auf die er zeigt!) liegen auf dem 
Stack.

a und c sind harmlos, weil sie ihren festen Platz im Speicher haben.
Das char[] ist problematisch, denn bei jedem Funktionsaufruf werden 
weitere 100 Bytes auf dem Heap belegt.

Korrektur:
1
void doSomething()
2
{
3
   char* d = new char[100];
4
   ...
5
   irgendwas mit d machen
6
   ...
7
   delete[] d;
8
}

Das ist wieder harmlos, weil die Funktion den Heap am Ende wieder frei 
gibt.

Problematischer wird es, wenn man unterschiedlich große Blöcke auf dem 
Heap belegt, diese nur zum Teil abbaut und dann wieder neu Speicher 
belegt. Dann tritt unter Umständen die Fragmentierung auf. Das ist in 
dem verlinkten Artikel detaillierter beschrieben.

Die String Klasse ist dafür berüchtigt. Denn sie belegt automatisch neue 
größere Speicherbereiche auf dem Heap, wenn nötig. Wenn du mehrere 
Strings zeitlich überlappend benutzt und in der Größe veränderst, hast 
du ein hohes Risiko der Heap Fragemntierung.

Wenn du 10 Strings als dauerhaft existente Puffer (mit wechselnder 
Größe) hast, dann ist das Risiko sogar sehr hoch.

Deswegen meide ich die String Klasse. Ich benutze lieber globale (oder 
statische) Puffer mit fester Größe. Sie werden nur einmal beim 
Programmstart angelegt und geben ihren Platz danach nie wieder frei. Ich 
bin ja nicht gezwungen, die Puffer immer rappelvoll zu machen.

Bei µC ist es besser, Puffer mit fester Größe anzulegen und zu behalten, 
als den Speicher immer wieder je nach Bedarf umzubauen.

von Christian J. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Die String Klasse ist dafür berüchtigt. Denn sie belegt automatisch neue
> größere Speicherbereiche auf dem Heap, wenn nötig. Wenn du mehrere
> Strings zeitlich überlappend benutzt und in der Größe veränderst, hast
> du ein hohes Risiko der Heap Fragemntierung.

Naja, ich verwende diese Klasse gerade. Und NMEA Daten sind ja auch 
heftig unstetig. Der ProMini dudelt aber die 5 Tage aber mit tausenden 
Telegrammen problemlos durch, ohne dass er sich aufhängt oder resettet. 
Entwder es läuft oder es läuft nicht oder es läuft nur eine Zeit lang. 
Diese Möglichkeiten gibt es nur in der uC Welt, vor 25 Jahren als ich 
anfing mit dem 80535 und heute auch noch mit den Cortexen usw. Binäre 
Bäume mit Zeigern würde ich nicht gerade in einem AVR aufbauen 
wollen....

von Stefan F. (Gast)


Lesenswert?

Es hängt halt stark davon ab, wie man die Strings benutzt. Sie sind 
nicht grundsätzlich schlecht.

Aber gerade für Anfänger sind sie für Überraschungen gut, weil man eben 
noch nicht genau weiß, wie die Klasse mit dem Speicher umgeht.

von Stefan F. (Gast)


Lesenswert?

Es gibt einen langen Aufsatz dazu: 
https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/

Mit der Methode String::Reserve() kannst du von Anfang an eine gewisse 
Menge Speicher reservieren. Reserviere bei langlebigen Strings 
mindestens so viel wie du jemals brauchen wirst, dann bekommst du kein 
Problem mit Heap Fragmentierung.

Denn (und das steht leider nicht in der Arduino Doku): Strings wachsen 
automatisch, aber sie verkleinern sich nicht automatisch.

von Jadiedeutschesprache (Gast)


Lesenswert?

Christian J. schrieb:
> Der ProMini dudelt aber die 5 Tage aber mit tausenden
> Telegrammen problemlos durch, ohne dass er sich aufhängt oder resettet.
> Entwder es läuft oder es läuft nicht oder es läuft nur eine Zeit lang.

ROFL.

Die Aussage eines "professionellen" Programmierers: "geht doch,
also kann's nicht an meiner Software liegen".

Ich kenne professionelle Firmware auf einem grösseren
Mikrocontroller. Dort werden per Definition für einen TCP Stack
nur eine begrenzte Anzahl von festgelegten, reservierten Speicher-
segmenten verwendet um so die Gefahr des Speicherüberlaufs durch
Fragmentierung zu vermeiden. Wenn man halt alle Speichersegmente
ausgenutzt hat dann gibt's halt keinen neuen Speicher (das kann
man zielgerecht abfangen und fehlerbeandeln), aber weder "kracht"
es dann noch hängt sich der Controller dann dadurch auf.

Stefan ⛄ F. schrieb:
> Mit der Methode String::Reserve() kannst du von Anfang an eine gewisse
> Menge Speicher reservieren. Reserviere bei langlebigen Strings
> mindestens so viel wie du jemals brauchen wirst, dann bekommst du kein
> Problem mit Heap Fragmentierung.

Der TO hat wohl ein anderes Problem: Er bekommt soviele Daten
eingeballert dass er mit der Ausgabe von Strings nicht hinter-
herkommt und damit immer ein Überlauf auftreten kann. Wenn es die
String-Klasse schafft dann kann man ja die Baudrate erhöhen. Aber
ich fürchte durch die Interrupt-Steuerung der Zeichenausgabe ist
die String-Klasse so träge dass sie es nicht schafft angemessen
zu reagieren.

von Christian J. (Gast)


Lesenswert?

Und wie würde das für meine Routine ausschauen mit ::Reserve? Der String 
rauscht vorher durch 2 Zeiger durch und dann wird es etwas 
undurchsichtig :-( ich meine mal so.... das Mopped läuft.... zumindest 
auf dem Schreibtisch.
1
void BufferWrite(String *str)
2
{
3
  #define ELEMENTS 3
4
  static String msg[ELEMENTS];
5
  static byte bufptr = 0;
6
7
  msg[bufptr] = *str;
8
  bufptr = (bufptr + 1) % ELEMENTS;
9
10
  /* Puffer voll, dann wegschreiben */
11
  if (!bufptr) {
12
      for (byte i = 0; i < ELEMENTS; i++) {
13
        SoftSerial.print(msg[i]);
14
      }
15
      f_WriteToCard = true;
16
      digitalWrite(LED_BUILTIN,1);                     
17
  }
18
}

Aufrufer
1
void CheckValidFix(String *t)
2
{
3
       ............viel Code .....
4
       BufferWrite(&(*t)); 
5
        ...........noch mehr Code......
6
}

Aufrufer aus Main
1
String c = SoftSerial.readStringUntil('\n');
2
        CheckValidFix(&c);

von Jadiedeutschesprache (Gast)


Lesenswert?

Christian J. schrieb:
> SoftSerial.print(msg[i]);

Ich glaub's ja nicht.

Das ist ja wirklich pervers. Mit SoftSerial? Rechenzeit-
aufwendiger gehts ja nun wirklich nicht. Wenn ich schon keine
Zeit habe dann nehme ich doch wenigstens die höchstmögliche
Baudrate und schreibe selbst auf die UART-Register um Zeichen
auszugeben.

Ich denke da wirst du noch längere Zeit in dich gehen (müssen).

von Christian J. (Gast)


Lesenswert?

Jadiedeutschesprache schrieb:
> Das ist ja wirklich pervers. Mit SoftSerial?

Wenn man keine Ahnung hat sollte man mal die F... ? :-)

Die UART kannste vielleicht gar nicht verwenden? Weil die dummerweise 
deine Debug Ausgabe ist? Und mit ner roten LED komme ich leider nicht 
hin da das Ganze etwas komplexer ist. Und bei 9600 baud klappts auch mit 
der soften Version dass openlog noch mitkommt.

Das ist die SoftSerial von Stoffregen, die ist sehr fix und duplex!

https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html

Die eingebaute ist ja so, als fahre man Porsche mit 38PS unter der 
Haube.
Das spielt alles schon ganz gut aber vielleicht geht es noch besser?

von Jadiedeutschesprache (Gast)


Lesenswert?

Christian J. schrieb:
> Das ist die SoftSerial von Stoffregen, die ist fix und duplex!

Softserial, egal welches, muss entweder timergesteuert, interrupt-
gesteuert oder Delay-gesteuert die zeitlich richtigen Flanken
erzeugen. Egal welche Methode, sie kostet signifikant Rechenzeit.

Zudem bringt die Methode auch noch eine Menge mehr an Code mit sich.

Aber träum ruhig weiter. Du bist auf dem richtigen Weg.

von Christian J. (Gast)


Lesenswert?

Jadiedeutschesprache schrieb:
> Zudem bringt die Methode auch noch eine Menge mehr an Code mit sich.
>
> Aber träum ruhig weiter. Du bist auf dem richtigen Weg.

Der Dislike ist jetzt von mir.

1. Solange der Code in den AVR 328P passt ist mir das sowas von wumpe!
2. Solange es keinen Flaschenhals gibt ebenfalls.

Korrekt: Timer 1 und INT hoch/runter Flanke. Weiss ich. Wie auch sonst?

Träum auch weiter... ich tue es seit ca 30 Jahren mit zigtausenden 
Zeilen Code, der teilweise auch in den Autos von Volvo läuft. Nach MISRA 
natürlich.

von Einer K. (Gast)


Lesenswert?

Christian J. schrieb:
> Und wie würde das für meine Routine ausschauen mit ::Reserve?
1
// global
2
String buffer[10];
3
4
// in setup()
5
for(String &s:buffer) s.reserve(100);

von Christian J. (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> for(String &s:buffer) s.reserve(100);

Habe ich ja noch nie gesehen sowas im for Teil. Geht das hier auch?

String msg[4]

for (int i=0;i<ELEMENTS;i++)
    msg[i].reserve(100);

von Einer K. (Gast)


Lesenswert?

Christian J. schrieb:
> Habe ich ja noch nie gesehen sowas im for Teil.
Das ist der "range based for loop".

Mit die schlimmsten und bösesten Fehler, sind aus dem Ruder laufende 
Zeiger oder Array Indizes.

Mein Rat:
Nutze diese for Variante, denn sie vermeidet diese Probleme.
Wo immer es geht.

Christian J. schrieb:
> Geht das hier auch?
Klar!
Aber warum, wenn es doch besseres gibt....

von Stefan F. (Gast)


Lesenswert?

Christian J. schrieb:
> String msg[4]
> for (int i=0;i<ELEMENTS;i++)
>     msg[i].reserve(100);

Unvorteilhaft ist allerdings die 4 versus ELEMENTS. Was pasiert wohl, 
wenn ELEMENTS != 4 ist?

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.