strtok, strtol ersetzen durch sparsamere Varianten

Gast #4422031
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?
Gast
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.
Gast
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.
Moderator Persönliche Seite
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...
Moderator Persönliche Seite
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.
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.
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.

Antwort schreiben

Eine eigene Frage? Dieser Thread ist über 10 Jahre alt. Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.

Neuen Thread starten

Eine Lösung, Ergänzung oder Rückmeldung zu diesem Thread? Dann antworte hier.

Formatierung
  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] – Link zu einem Artikel
  • Verweis auf einen anderen Beitrag: Rechtsklick auf dessen Beitrags-ID (#8078628), „Adresse kopieren“, in den Text einfügen

Mehr zur Formatierung

Längeren Sourcecode als Anhang, nicht in den Text. JPG nur für Fotos und Scans; Zeichnungen und Screenshots als PNG oder GIF. Siehe Bildformate.

Schon registriert? Anmelden

Wird nicht angezeigt. Wir schicken dir einen Link; sobald du damit bestätigst, erscheint dein Beitrag.

Mit dem Abschicken legst du einen kostenlosen Account an und erkennst die Nutzungsbedingungen und die Datenschutzerklärung an.