Zeichenketten verbinden

hallo,

ich mal wieder, bin grad dabei mich in die verschiedenen zeichenkettenoperationen mit c/c++ einzuarbeiten.

leider ist mir da noch nicht so alles klar, ich habe dazu mal verschiedene möglichkeiten versucht.
insbesondere der block vor dem serial.println bereit mir noch schwierigkeiten.

ich suche eine möglichkeit an vielen stellen zeichenketten aufzubereiten um diese später zu versenden, das muss natürlich so speicherschonend wie möglich passieren.

bei s(n)printf kann man ja so schön mit %s oder %i die zeichenketten verbinden, leider brauchen diese funktionen auch entsprechend viel speicher.

ich habe es aber auch nicht geschafft, z.b. diesen formatierungsstring und die char-arrays an eine funktion zu übergeben, um ggfs speicher zu sparen (ich gehe davon aus, dass der vielfache aufruf von z.b. s(n)printf entsprechend speicher benötigt, während der funktionsaufruf eigentlich weniger brauchen sollte.

weiterhin habe ich es nicht geschafft, z.b. den formatierungsstring per F() oder PSTR() "auszulagern".

könnte mir jemand bitte den weg weisen?

// Die Arraygrößen sind bewusst so gewählt!

char test1[100] = "Dies ist ein Test";
char test2[50]  = "der einen Satz ausgibt";
char test3[150] = "";

byte bwert1 = 128;  // Soll als Ascii-Ziffer ausgegeben werden
byte bwert2 = 0;    // Soll als Ascii-Ziffer 0 = Ascii-Code 48 ausgegeben werden

void setup()
{
  Serial.begin(115200);

  /*
    snprintf(test3, 150, "%s %s", test1, test2);
    Der Sketch verwendet 3.470 Bytes (10%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
    Globale Variablen verwenden 506 Bytes (24%) des dynamischen Speichers, 1.542 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.
  */

  /*
    sprintf(test3, "%s %s", test1, test2);
    Der Sketch verwendet 3.436 Bytes (10%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
    Globale Variablen verwenden 506 Bytes (24%) des dynamischen Speichers, 1.542 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.
  */

  /*
    test3[0] = '\0';
    strcat(test3, test1);
    strcat(test3, " ");
    strcat(test3, test2);

    Der Sketch verwendet 2.000 Bytes (6%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
    Globale Variablen verwenden 502 Bytes (24%) des dynamischen Speichers, 1.546 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.
  */

  /*
    memset(&test3[0], 0, sizeof(test3));
    strcat(test3, test1);
    strcat(test3, " ");
    strcat(test3, test2);
    Der Sketch verwendet 2.008 Bytes (6%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
    Globale Variablen verwenden 502 Bytes (24%) des dynamischen Speichers, 1.546 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.
  */

  /*
    // Nur mal ein Test um die Mehrfachverwendung zu testen

    memset(&test3[0], 0, sizeof(test3));
    strcat(test3, test1);
    strcat(test3, " ");
    strcat(test3, test2);

    Serial.println(test3);

    memset(&test3[0], 0, sizeof(test3));
    strcat(test3, test2);
    strcat(test3, " ");
    strcat(test3, test1);

    Serial.println(test3);

    memset(&test3[0], 0, sizeof(test3));
    strcat(test3, test1);
    strcat(test3, " ");
    strcat(test3, test1);

    Serial.println(test3);

    memset(&test3[0], 0, sizeof(test3));
    strcat(test3, test2);
    strcat(test3, " ");
    strcat(test3, test2);

    Der Sketch verwendet 2.164 Bytes (6%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
    Globale Variablen verwenden 502 Bytes (24%) des dynamischen Speichers, 1.546 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.
  */

  /*
    memset(&test3[0], 0, sizeof(test3));
    strcat(test3, test1);
    strcat(test3, " ");
    strcat(test3, bwert2);

    invalid conversion from 'byte {aka unsigned char}' to 'const char*' [-fpermissive]
  */

  Serial.println(test3);

  /*
    Alle Blöcke vorher kommentiert, nur Serial.println wird verwendet

    Der Sketch verwendet 1.786 Bytes (5%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
    Globale Variablen verwenden 350 Bytes (17%) des dynamischen Speichers, 1.698 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.
  */
}

void loop()
{
}

grüße
fly

FlyingEagle:
bei s(n)printf kann man ja so schön mit %s oder %i die zeichenketten verbinden, leider brauchen diese funktionen auch entsprechend viel speicher.

Von was für Speicher redest Du? Flash-Speicher (Programmspeicher) wird benötigt, um das Programm beim Hochladen im Chip zu speichern. Ein UNO hat davon 32KB.Oder RAM-Speicher wird benötigt, um im Programm statische Variablen, dynamische Variablen und den Stack zur Programmlaufzeit zu speichern. Ein UNO hat davon 2KB (2048 Bytes).

FlyingEagle:
weiterhin habe ich es nicht geschafft, z.b. den formatierungsstring per F() oder PSTR() "auszulagern".

Beispiel dafür:

void setup()
{
char test1[100] = "Dies ist ein Test";
char test2[50]  = "der einen Satz ausgibt";
char test3[150] = "";
  Serial.begin(115200);
  snprintf_P(test3, sizeof(test3), PSTR("%s %s"), test1, test2);
  Serial.println(test3);
}

void loop()

Die Funktion, um Zeichenketten zu verbinden, ist "strcat", während die Funktionen sprintf(), snprintf() bzw. snprintf_P() extrem mächtige (und umfangreiche) Formatierungsfunktionen sind, die mit ganz unterschiedlichen Datentypen umgehen können (beispielsweise signed und unsigned char, signed und unsigned int, signed und unsigned long, nur nicht standardmäßig mit float auf 8-bit AVR).

Es gibt von allen String Funktionen _P Versionen mit den man PROGMEM Strings verarbeiten kann. Bei printf() gibt es auch %S als Parameter um einen PROGMEM zu übergeben

Es gibt auch itoa()/utoa() um Integer in Strings zu wandeln wenn du nur das brauchst. Das macht aber keine führenden Nullen oder Leerzeichen.

Wenn es drum geht, wenig RAM zu verbrauchen, wäre mein Tip, es einfach bleiben zu lassen:

  Serial.print(F("Dies ist ein Test"));
  Serial.println(F(", der einen Satz ausgibt"));

braucht am wenigsten RAM. ( Wird genauso schnell ausgegeben. )

Ja. Als erstes sollte man sich überlegen ob das wirklich in einem String stehen muss. Es gibt Libraries bei denen das nötig ist. Aber wenn man etwas nur über Serial versenden will ist das unnötiger Aufwand. Das kann man einfach mit wiederholten Aufrufen von print()/println() erledigen

Es geht nicht um das ausgeben via serial, es geht vielmehr darum die zusammengesetzte zeichenketten an eine funktion zu übergeben, die diese dann via ethernet ausgibt, der funktion muss ein char array übergeben werden.

zum formatierungsstring selbst, der ist ja nunmal ein string, und kann m.e./m.w. NICHT mit F() in den flash ausgelagert werden. zumindest habe ich das noch nicht geschafft.

mir nützen keine formatierungs-kommandos wie %S wenn ich ja genau vom formatierungsstring rede.

@jurs, ich denke ich rede vom ram, also dem dynamischen speicher zur laufzeit und natürlich auch vom speicher für programmcode, da hier vermutlich mehr overhead mitkommt, als ich am ende brauche.

führende nullen sind mir soweit egal, ich muss nur in das array irgendwie die zahlen als ascii-ziffern reinkriegen.

ist denn meine mehrfachverwendung von strcat soweit richtig und notwendig? man kommt sich so "komisch" vor, wenn man das über 2,3,4,5 zeilen so zusammenbaut (es kommt ja das richtige raus).

auch wäre eine antwort auf die frage: "mehrfach s(n)print/strcat" verwenden oder das in eine funktion kapseln? noch sehr nett. (wenn kapseln, wie dann z.b. den formatierungsstring übergeben, dass er auch noch funktioniert).

PS: vielleicht nochmal so am rande, viele beispiele die man so im internet findet, geben häufig den ganzen kram per serial aus, wenige programme verwenden zeichenketten (sage hier bewusst nicht strings) später in anderen teilen des programmes wieder. daher ist es mir oft nicht so leicht passende beispiele zum lernen zu finden.
z.B. hier

Auch bei der Standard Arduino Ethernet Bibliothek reicht es die Strings per print()/println() zu stückeln

zum formatierungsstring selbst, der ist ja nunmal ein string, und kann m.e./m.w. NICHT mit F() in den flash ausgelagert werden.

F() ist ein Arduino Makro! Das ist der Grund weshalb das nicht mit den Standard C Funktionen zusammenarbeitet. Das geht aber auch wenn man weiß wie. F() ist nur ein Hilfs Makro damit man einen eindeutigen Typ hat. Das wird dann am Ende nur auf PGM_P gecastet (was wiederum nur ein Makro für const char* ist).

Einen String der per PSTR() oder PROGMEM deklariert wurde kannst du einfach an einen const char* als Parameter übergeben. Dann kannst du das allerdings nicht von einem const char* unterscheiden der ins RAM zeigt. Das ist der Grund weshalb F() existiert.

Hier gibt es zwei Optionen:

void setup()
{
  Serial.begin(9600);

  funcFlashString(F("%s"));
  funcConstChar(PSTR("%s"));
}

void loop()
{
}

void funcFlashString(const __FlashStringHelper* fmt)
{
  char buffer[20];
  const char* fmt_c = (PGM_P)fmt;   //braucht man nicht unbedingt in einer extra Zeile
  snprintf_P(buffer, sizeof(buffer), fmt_c, "__FlashString");
  Serial.println(buffer);
}

void funcConstChar(const char* fmt)
{
  char buffer[20];
  snprintf_P(buffer, sizeof(buffer), fmt, "const char*");
  Serial.println(buffer);
}

Wenn du an die Funktion mit einem const char* aus Versehen einen Stirng im RAM übergibst kommt Unsinn heraus.

Hier sollte man sich mal ansehen was __FlashStringHelper und F() eigentlich sind. Das ist einfach und recht genial:

class __FlashStringHelper;
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))

Es ist nur die Vorraus-Deklartion einer Klasse. Und F() castet einen PROGMEM String auf diese Klasse. Um den String dann zu verwenden muss man nur den Cast rückgängig machen

@jurs, ich denke ich rede vom ram, also dem dynamischen speicher zur laufzeit und natürlich

Mit dynamischem Speicher hat das nichts zu tun. Diese Strings werden statisch zur Compile-Zeit angelegt. Dass die Arduino IDE das "dynamischen Speicher" nennt ist ein Fehler

@serenifly, merci für die ausführliche antwort.

leider bleiben für den moment noch ein paar offenen fragen:

  • wie verbinde ich das char-array per strcat mit nem byte? [bytewert + 48 kann ich strcat nicht übergeben]
  • sind die mehrfachverwendungen von strcat so ok, oder brauchen die am ende mehr speicher als wenn ich sprintf mit z.b. %s%s%s%s verwende (was am ende 4xstrcat bedeuten würde) [in meinem fall müssten zwischen verschiedene werte "/" eingefügt werden, so das am ende z.b. abc/cde/fgh/ijk rauskommt]
  • sind mehrfach-funktionsaufrufe (sprich das kapseln von immer gleichen funktionen in einer funktion[!] mit parameterübergabe) speicherschonender?

FlyingEagle:

  • wie verbinde ich das char-array per strcat mit nem byte? [bytewert + 48 kann ich strcat nicht übergeben]

Sowas?

void setup() 
{
  Serial.begin(9600);
  
  char buffer[30] = { 0 };
  char delimiter[] = "/";
  char byteStr[2] = { 0, 0 };

  byteStr[0] = 1 + '0';
  strcat(buffer, byteStr);
  strcat(buffer, delimiter);
  byteStr[0] = 2 + '0';
  strcat(buffer, byteStr);
  strcat(buffer, delimiter);
  byteStr[0] = 3 + '0';
  strcat(buffer, byteStr);

  Serial.println(buffer);
}

void loop() 
{
}
  • sind die mehrfachverwendungen von strcat so ok, oder brauchen die am ende mehr speicher als wenn ich sprintf mit z.b. %s%s%s%s verwende (was am ende 4xstrcat bedeuten würde)

Wieso soll das mehr Speicher brauchen? Es wird halt mehrmals durch den String iteriert, da die Funktion erst mal das Ende finden muss. Dass man einen Kompromiss zwischen Speicher- und Zeitverbrauch eingehen muss kommt oft vor

[in meinem fall müssten zwischen verschiedene werte "/" eingefügt werden, so das am ende z.b. abc/cde/fgh/ijk rauskommt]

Bei der Standard Arduino Ethernet Library würde ich einfach mehrmals print() machen. Oder das nehmen:
http://arduiniana.org/libraries/streaming/

Da kann man einfach sowas machen wie in C++:

client << value1 << '/' << value2 << '/' << endl;

Geht mit allen Klassen die print()/println() können

Oder was für dich auch sehr nützlich wäre:
http://arduiniana.org/libraries/pstring/

Damit kann man mit den print() Funktionen der Arduino Print Klasse in ein char Array schreiben! Da kannst du ganz einfach str.print('/') und str.print(value) machen. Oder mit der Streaming Library dazu - dann geht einfach <<

  • sind mehrfach-funktionsaufrufe (sprich das kapseln von immer gleichen funktionen in einer funktion[!] mit parameterübergabe) speicherschonender?

Jeder Funktionsaufruf braucht etwas Platz auf dem Stack für Rücksprung Adresse und die Sicherung von Registern.

Serenifly:
Sowas?

siehe den code in meinem eingangspost

ich verwende diese

Probier mal ob die PString Klasse auf dem ESP läuft

Das unterstützt sogar printf() wenn man es wirklich braucht:

// Safe access to sprintf-like formatting, e.g. str.format("Hi, my name is %s and I'm %d years old", name, age);
int format(char *str, ...);

Was aber bei dir nicht wirklich der Fall ist

Und man kann mit += konkatenieren:

  // Concatenation str += myfloat;
  template<class T> inline PString &operator +=(T arg) 
  { print(arg); return *this; }

Das ist ein Template. Also kann man beliebige Datentypen übergeben

ESP? edit: nu is wech ...

so sieht es aktuell in meinem sketch aus :frowning:

Der Sketch verwendet 25.998 Bytes (80%) des Programmspeicherplatzes. Das Maximum sind 32.256 Bytes.
Globale Variablen verwenden 1.601 Bytes (78%) des dynamischen Speichers, 447 Bytes für lokale Variablen verbleiben. Das Maximum sind 2.048 Bytes.
Wenig Arbeitsspeicher verfügbar, es können Stabilitätsprobleme auftreten.

Sorry, da war ich durcheinander gekommen. Soll ja wohl auf einem UNO/AVR laufen. Aber PString wäre für dich ideal. Da brauchst du dich nicht um die Details zu kümmern und die Formatierung übernimmt die Arduino Print Klasse für dich

Serenifly:
die Arduino Print Klasse für dich

die eh schon an board ist, da ich serial verwende?

Teile davon. Methoden werden nur kompiliert wenn sie auch wirklich verwendet werden. Aber ja, es kann sein dass man so Code teilen kann. :slight_smile:

ich bau die pstring mal ein, mal gucken was passiert.

kann man sowas noch auslagern?

char mUsername[] = "xxxxxx";

will/wollte gern einige variablen global definieren, damit ich beim übertragen auf andere unos nicht jedesmal überall im code suchen muss. zudem habe ich einige ähnliche chars[] definiert mit werten die an mehreren stellen verwendet werden ...

Ich habe mir ein P() Makro gemacht:

char stringBuffer[30];

#define P(str) strcpy_P(stringBuffer, PSTR(str))

Das kann man dann ähnlich wie F() verwenden (da strcpy() einen Zeiger auf den Puffer liefert!), aber mit beliebigen Funktionen. Der String wird automatisch mit PSTR() ins Flash geschrieben und per strcpy_P() in den Puffer kopiert.

Der Puffer belegt erst mal RAM, aber den kann man wiederverwenden. Wenn mehr als 30 Bytes String Literale verwendet - was gleich passiert ist - spart man RAM

Aber Achtung: eine Längenprüfung findet nicht statt!

Ansonsten kannst du auch globale Strings einfach ins Flash packen:

const char mUsername[] PROGMEM = "xxxxxx";

Dann musst du sie nur bei der Verwendung ins RAM kopieren. z.B.:

#define ProgmemCopy(str) strcpy_P(stringBuffer, str)

Serenifly:
Dann musst du sie nur bei der Verwendung ins RAM kopieren. z.B.:

#define ProgmemCopy(str) strcpy_P(stringBuffer, str)

wie muss str definiert sein?

Wir reden hier immer noch von C Strings. Halt einer der im PROGMEM steht. z.B.:

const char mUsername[] PROGMEM = "xxxxxx";

Wenn man es sicherer haben will kann man hier übrigens auch strncpy_P() verwenden:

#define P(str) strncpy_P(stringBuffer, PSTR(str), sizeof(stringBuffer) - 1)
#define ProgmemCopy(str) strncpy_P(stringBuffer, str, sizeof(stringBuffer) - 1)

Dann kann man das machen:

Serial.println(P("Test"));
Serial.println(ProgmemCopy(mUsername));

Bei Serial wäre das unnötig. Aber andere Funktionen wie deine Ethernet Library können halt nur mit Strings im RAM umgehen.

Bei den C String Funktionen hast du dagegen wieder Alternativen. Da gibt es z.B. auch strcat_P() um einen String anzuhängen der im Flash steht:
http://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html

was kneift mich hier?

definition in der pubsub-lib:

boolean connect(const char* id, const char* user, const char* pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage);

alle variablen hier sind als char[] PROGMEM definiert.

das geht nicht:

  boolean success = mqtt.connect(ProgmemCopy(mqttClientId),
                                 ProgmemCopy(mqttUsername),
                                 ProgmemCopy(mqttPassword),
                                 mqttTopic2Send,
                                 mqttLwtQos,
                                 mqttLwtRetain,
                                 ProgmemCopy(mqttLwtMessage));

das hier geht:

  boolean success = mqtt.connect(ProgmemCopy(mqttClientId),
                                 "ardard",
                                 "secret",
                                 mqttTopic2Send,
                                 mqttLwtQos,
                                 mqttLwtRetain,
                                 ProgmemCopy(mqttLwtMessage));

ich habe mir die werte per serial ausgegeben, sieht soweit alles unverdächtig aus - für mich ...