Forum: Mikrocontroller und Digitale Elektronik strtok, strtol ersetzen durch sparsamere Varianten


von Heribert (Gast)


Lesenswert?

Hallo,

mal eine kurze Frage in die Runde. Gibt es irgendwo eine Code-Sammlung, 
wo man alternative Funktionen findet, die deutlich weniger Flash/RAM 
brauchen?

Ich habe zum Beispiel hier das Problem, dass diese doch recht kurze 
Funktion einen riesigen Speicherzuwachs verursacht.
1
  const char delimiter[] = ",;:";      // Trennzeichen
2
  const unsigned char SIZE_OF_DATA = 5;  // Maximal zu empfangende Datenfelder
3
  unsigned char data[SIZE_OF_DATA];    // Speicher Datenfelder
4
  unsigned char ct = 0;
5
6
  char* ptr = (char*)strtok((char*)USART0_string, delimiter);
7
  while(ptr != NULL && ct < SIZE_OF_DATA)
8
  {
9
    data[ct++] = strtol(ptr, NULL, 16);
10
    ptr = strtok(NULL, delimiter);
11
  }

Ich empfange auf einem Attiny einen String, der immer wie folgt aussieht

char buffer[] = "01:02:0f:0F:FF\n";

Nun habe ich das in meiner Anweisung bislang so gemacht,
das ich jedes Mal eine Zeichenkette bis zum Auftreten des Trennzeichens 
einlese. Die Zeichenkette (in meinem Fall im 2 Zeichen lang + '\0') wird 
dann mit strtol in eine Zahl umgewandelt zur Basis 16. Anschließend 
landet diese in einem Zahlenarray. Sollten einmal, wieso auch immer mehr 
Datenfelder eingelesen werden, so werden die weiteren ignoriert.

Kennt jemand dazu eine smarte Alternative?

von DirkB (Gast)


Lesenswert?

Benutze endptr von strtol
1
  char *endptr;
2
  char* ptr = (char*)USART0_string;
3
  do
4
  {
5
    data[ct++] = strtol(ptr, &endptr, 16);
6
    ptr = endptr+1;
7
  }
8
  while(*endptr == ':' && ct <= SIZE_OF_DATA)
oder so (weil ungetestet)
Geht aber nur, wenn der : direkt hinter der Zahl steht.

von Heribert (Gast)


Lesenswert?

Hallo Dirk,

danke schon einmal für den Tipp. So funktionierte es nicht direkt. Gab 
aber keine Compilerwarnung, werde es gleich noch einmal weiter testen. 
Es würde auf jedenfall schon mal ein kleines bisschen am Speicher 
sparen.

Würde aber gerne komplett auf string.h und stdlib.h verzichten können. 
Das ist die einzige Stelle im ganzen Programm, wo ich das benötige.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Ich habe aus dem obigen Code-Schnipsel mal ein Programm gemacht, welches 
einfach auf einem PC (Linux, Windows) übersetzbar ist:
1
#include <stdio.h>
2
#include <string.h>
3
#include <stdlib.h>
4
5
char buffer[] = "01:02:0f:0F:FF\n";
6
7
int main ()
8
{
9
    const char delimiter[] = ",;:";      // Trennzeichen
10
    const unsigned char SIZE_OF_DATA = 5;  // Maximal zu empfangende Datenfelder
11
    unsigned char data[SIZE_OF_DATA];    // Speicher Datenfelder
12
    unsigned char ct = 0;
13
14
    char* ptr = buffer;
15
16
    while(ptr != NULL && ct < SIZE_OF_DATA)
17
    {
18
        data[ct++] = strtol(ptr, NULL, 16);
19
        ptr = strtok(NULL, delimiter);
20
    }
21
22
    printf ("%s%02x:%02x:%02x:%02x:%02x\n", buffer, data[0], data[1], data[2], data[3], data[4]);
23
24
    return 0;
25
}

Output:

01:02:0f:0F:FF
01:01:00:00:00

Manchmal ändert sich die 2. Zeile auch.

Fazit: Das Programm tut überhaupt nicht, was es soll. Wurde das 
überhaupt mal getestet?

Alternative ohne die Kanonen auf Spatzen (strtok & strtol) folgt 
gleich...

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Und hier die platzsparende und schnellere Variante, die ohne strtok() 
und strtol() auskommt:
1
#include <stdio.h>
2
#include <stdint.h>
3
4
/*---------------------------------------------------------------------------------------------------------------------------------------------------
5
 * htoi: convert n hex digits (max is 4) of a string into decimal value
6
 *---------------------------------------------------------------------------------------------------------------------------------------------------
7
 */
8
uint16_t
9
htoi (char * buf, uint8_t max_digits)
10
{
11
    uint8_t     i;
12
    uint8_t     x;
13
    uint16_t    sum = 0;
14
15
    for (i = 0; i < max_digits && *buf; i++)
16
    {
17
        x = buf[i];
18
19
        if (x >= '0' && x <= '9')
20
        {
21
            x -= '0';
22
        }
23
        else if (x >= 'A' && x <= 'F')
24
        {
25
            x -= 'A' - 10;
26
        }
27
        else if (x >= 'a' && x <= 'f')
28
        {
29
            x -= 'a' - 10;
30
        }
31
        else
32
        {
33
            x = 0;
34
        }
35
        sum <<= 4;
36
        sum += x;
37
    }
38
39
    return (sum);
40
}
41
42
43
char buffer[] = "01:02:0f:0F:FF\n";
44
45
int main ()
46
{
47
    const unsigned char SIZE_OF_DATA = 5;  // Maximal zu empfangende Datenfelder
48
    unsigned char data[SIZE_OF_DATA];    // Speicher Datenfelder
49
    unsigned char ct = 0;
50
51
    char* ptr = buffer;
52
53
    while (*ptr)
54
    {
55
        data[ct++] = htoi (ptr, 2);
56
        ptr += 3;
57
    }
58
59
    printf ("%s%02x:%02x:%02x:%02x:%02x\n", buffer, data[0], data[1], data[2], data[3], data[4]);
60
61
    return 0;
62
}

Output:
01:02:0f:0F:FF
01:02:0f:0f:ff

Passt also.

Voraussetzung ist aber, dass der TO das Format von Buffer 
("xx:xx:xx:xx:xx\n") vorher prüft. Sonst geht das hier in die Hose.

von Sicher (Gast)


Lesenswert?

Ich empfehl die Substitution zu Laengenenthaltenden Strings. Dh, dass 
das erste Byte die Laenge < 256 enthaelt. Macht vieles einfacher.

von DirkB (Gast)


Lesenswert?

@Frank M.

In deinem ersten Beispiel fehlt der initiale Aufruf von strtok (da ist 
der erste Paramter != NULL)

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

DirkB schrieb:
> @Frank M.
>
> In deinem ersten Beispiel fehlt der initiale Aufruf von strtok (da ist
> der erste Paramter != NULL)

Stimmt, Du hast recht, den habe ich versehentlich gelöscht.

Nichtsdestotrotz: Meine gepostete Alternative ist kürzer und schneller 
:-)

von Stefan S. (sschultewolter)


Lesenswert?

Hallo Frank,

klasse Ansatz. Hab mir deine Idee auch erst einmal direkt abgespeichert. 
Habe es bei mir ähnlich gelöst mit string und stdlib, da es mir so im 
Buch auch nicht anders erklärt wurde. Leider findet meisten keine 
Alternativen und muss die selber schreiben.

Ich werde deine Idee mal versuchen aufzuarbeiten. Habe auch immer einen 
String, den ich splitten muss. Bekomme aber ab und zu ein paar falsche 
Werte rein. Mal schaun ob ich das evtl. etwas passend umschreiben kann 
;)

Werd dann meinen Ansatz hier auch noch mit reinstellen.

von Lukas K. (carrotindustries)


Lesenswert?

Woher kommt eigentlich dieses Märchen, dass alles was aus der libc kommt 
aufgeblasen ist?

Mal das strtok von der newlib als Beispiel: 
https://sourceware.org/git/gitweb.cgi?p=newlib-cygwin.git;a=blob_plain;f=newlib/libc/string/strtok_r.c;hb=HEAD

von Stefan S. (sschultewolter)


Lesenswert?

Hab das ganze mal etwas umgeschrieben, etwas statisch, aber sollte nicht 
das Problem sein.

Mit
1
// CMD_ERROR == 0x00
2
uint8_t checkCommand(char *str)
3
{
4
  const uint8_t MAX_STRLEN = 11;
5
  uint8_t ct = 0;
6
  
7
  while(*str != '\0')
8
  {
9
    if(ct%3 != 2)
10
    {
11
      if(!
12
      ((*str >= 'a' && *str <= 'f') ||
13
      (*str >= 'A' && *str <= 'F') ||
14
      (*str >= '0' && *str <= '9'))
15
16
      ) return CMD_ERROR;
17
    }
18
    else
19
    {
20
      if(*str != ':') return CMD_ERROR;
21
    }
22
    ct++;
23
    *str++;
24
  }
25
  if(ct != MAX_STRLEN) return CMD_ERROR;
26
  else return 1;
27
}
wird überprüft, ob der String = "xx:xx:xx:xx" entspricht.

Wenn diese Funktion 1 zurückgibt, kann splitCommand genutzt werden.
1
void splitCommand(char *str, uint8_t *data, uint8_t SIZE_DATA)
2
{
3
  uint8_t ct = 0;
4
  char* ptr = str;
5
  while(*ptr && ct < SIZE_DATA-1)
6
  {
7
    data[ct++] = htoi(ptr, 2);
8
    ptr += 3;
9
  }
10
}
Scheint soweit zu gehen.

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.