Forum: Compiler & IDEs String parsen und zahlen Zahlen per "atoi" extrahieren klappt nicht


von Folio (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Leute,

ich versuche gerade an einen µC seriell eine ganz einfache 
Steuerbefehlskette zu übersenden und scheitere gerade am sehr simplen 
Parser!

Ich will an einen µC einen RGBW wert übermitteln um eben drei 
Konstantstromquellen entsprechend mit PWM zu dimmen.

Übertragen werden sollen immer vier zahlen von 0-255, stellvertretend 
für den PWM Wert, mit einem Komma separiert und mit * startend und # 
endend.

z.B. so:  *127,255,001,111#

Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an 
der richtigen stelle stehen. Das klappt auch!

Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig 
per "atoi" zu extrahieren.

Aus dem Beispiel *127,255,001,111# extrahiert mir mein Progrämmchen 
127,255127, 1255127, -667894569 statt 127,255,1,111 :-(

Ich hab mir dazu das im Anhang angehängte "Demo" geschrieben um zu 
prüfen, ob es das so tut, bevor ich es auf den µC portieren kann.
Es wäre super, wenn sich jemand geübteres als ich sich das mal ansehen 
könnte und mir einen Tipp oder einen Wink mit dem Zaunpfahl geben 
könnte, denn ich sehe den Fehler einfach nicht :-(

von Arc N. (arc)


Lesenswert?

Wie sind Zeichenketten in C aufgebaut?
Wie viel Platz ist in Rbuffer, Gbuffer etc.?
Was parst/wandelt atoi dann u.U. alles bzw. was fehlt?

Hint: Welches Verhalten hat atoi, wenn nach einer Zahl/Ziffer noch 
andere Zeichen im String enthalten sind? Sind die Buffer dann noch 
nötig?

: Bearbeitet durch User
von Folio (Gast)


Lesenswert?

Arc N. schrieb:
> Wie sind Zeichenketten in C aufgebaut?
? als Array aus Chars?


Arc N. schrieb:
> Wie viel Platz ist in Rbuffer, Gbuffer etc.?

Die R/G/B/W Buffer sind für drei zeichen gedacht eben von 000 bis 
theoretisch 999. Den Wertebereich muss ich später noch abfangen!

Ich dachte erst, ich hätte Probleme mit "001" da zwei vorgestellte 
Nullen da sind, aber der Fehler passiert ja schon mit 255.

Arc N. schrieb:
> Was parst/wandelt atoi dann u.U. alles bzw. was fehlt?

Auch das weiß ich nicht :-( Ich denke ich muss noch viel lernen!

von DPA (Gast)


Lesenswert?

Ach du grüne nullbytes.

von Arc N. (arc)


Lesenswert?

Folio schrieb:
> Arc N. schrieb:
>> Wie sind Zeichenketten in C aufgebaut?
> ? als Array aus Chars?

Ja, mit ... ... am Ende (siehe Post über diesem ;))

> Arc N. schrieb:
>> Was parst/wandelt atoi dann u.U. alles bzw. was fehlt?
>
> Auch das weiß ich nicht :-( Ich denke ich muss noch viel lernen!

http://www.cplusplus.com/reference/cstdlib/atoi/

von W.S. (Gast)


Lesenswert?

Folio schrieb:
> Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an
> der richtigen stelle stehen. Das klappt auch!
>
> Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig
> per "atoi" zu extrahieren.

Dann extrahiere sie doch ganz einfach OHNE "atoi". Sowas sollte ja 
wirklich kein Thema sein. Lade dir einfach die Lernbetty herunter und 
guck dort in conv.c hinein, falls du so etwas nicht selber aus dem Ärmel 
schütteln kannst.

Und wer sowas nicht aus eigener Kraft kann und dennoch die Nase über die 
olle Lernbetty rümpft, dem ist nicht zu helfen.

W.S.

von foobar (Gast)


Lesenswert?

Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins 
größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0').

Einfacher kannst du aber atoi, der eh beim ersten ungültigen Zeichen 
aufhört, direkt auf den String loslassen:
1
int parse(char *str)
2
{
3
   if (strlen(str) > 16
4
     && str[0] == '*' && str[16] == '#'
5
     && str[4] == ',' && str[8] == ',' && str[12] == ',')
6
   {
7
      int r = atoi(str + 1);
8
      int g = atoi(str + 5);
9
      int b = aoti(str + 9);
10
      int w = atoi(str + 13);
11
      // mach was mit r,g,b,w
12
   }
13
   ...
14
}

Alternativ kannst du dir auch mal sscanf anschauen.

von Folio (Gast)


Lesenswert?

foobar schrieb:
> Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins
> größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0').

Warum sagt man das denn nicht einfach. Ich bin noch absoluter Anfänger 
:-( Ich kämpfe noch mit so vielen Baustellen Zeitgleich und nur 
kryptische Kommentare. Ich hab mir sogar mein eigenes "atoi" 
implementiert, das das selbe Ergebnis lieferte wie das echte :-/ ich war 
schon echt am verzweifeln.

Wie gesagt, ich muss noch viel lernen!
Danke!

von Carl D. (jcw2)


Lesenswert?

Folio schrieb:
> foobar schrieb:
>> Worauf dich die Leute hinweisen wollen, ist, dass deine Buffer eins
>> größer sein müssen (3 Zeichen plus die Stringende-Kennung '\0').
>
> Warum sagt man das denn nicht einfach. Ich bin noch absoluter Anfänger
> :-( Ich kämpfe noch mit so vielen Baustellen Zeitgleich und nur
> kryptische Kommentare. Ich hab mir sogar mein eigenes "atoi"
> implementiert, das das selbe Ergebnis lieferte wie das echte :-/ ich war
> schon echt am verzweifeln.
>
> Wie gesagt, ich muss noch viel lernen!
> Danke!

Am besten lernt man, wenn man selber drauf kommt und das geht am besten, 
wenn man von jemand auf das richtige Gleis gesetzt wird, vielleicht 
sogar ganz dicht ans Ziel. Dann den letzten Schritt zum Erfolgserlebnis 
selber machen.
Wenn man hier also solche Antworten wie oben bekommt, dann geschieht das 
in bester Absicht.

von foobar (Gast)


Lesenswert?

> Warum sagt man das denn nicht einfach.

Du wolltest nen Tipp, den haben sie gegeben. Ich hab dann den Zaunpfahl 
rausgeholt ;-)

von Peter D. (peda)


Lesenswert?

Dein Code macht alles mögliche, aber nichts sinnvolles.
Am bequemsten kannt Du mit sscanf() die 4 Zahlen einlesen.
Du kannst aber auch in einer Schleife (0..3) mit strtok() den String 
zerlegen und mit atoi() dann einlesen. strtok() ersetzt den Delimiter 
"," durch "\0" und gibt für atoi() den Pointer auf den Teilstring 
zurück.

sscanf() ist eine mächtige und sehr komfortable Funktion. Es lohnt sich 
daher, sich damit zu beschäftigen und die Doku dazu zu lesen (gibt auch 
Tutorials dazu in deutsch).

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

strtok ist doch gar nicht nötig, da atoi auch beim ',' aufhört.

von foobar (Gast)


Lesenswert?

Hinweis: strtok sollte man aus seinem Repertoir streichen! Das ist 
eines der schwarzen Schafe im C-Standard.  Zum einen hat sie verborgenen 
State und zum anderen ändert sie den übergebenen String (das "const" 
beim ersten Parameter ist nicht nur einfach vergessen worden).  Am 
besten einfach ignorieren ;-)

von Peter D. (peda)


Lesenswert?

atoi braucht aber den Zeiger auf die nächste Zahl.
Sich darauf zu verlassen, daß immer 4 Bytes Abstand sind, kann einem 
später auf die Füße fallen.

von Peter D. (peda)


Lesenswert?

foobar schrieb:
> strtok sollte man aus seinem Repertoir streichen!

Es macht allerdings das Parsen von Kommandozeilen sehr bequem. Ich 
versuche erstmal Standardfunktionen zu verwenden, ehe ich mir was selber 
schreibe.

von foobar (Gast)


Lesenswert?

> Sich darauf zu verlassen, daß immer 4 Bytes Abstand sind

Hat ja keiner gesagt, dass die Aufgabenstellung besonders elegant wäre 
;-)  Zumindest wird vorher geprüft, dass etwas halbwegs sinnvolles 
vorhanden ist und das Programm nicht abstürzt. Besser geht immer - 
sscanf, strtoul, handgedengelt, ...

von foobar (Gast)


Lesenswert?

>> strtok sollte man aus seinem Repertoir streichen!
>
> Es macht allerdings das Parsen von Kommandozeilen sehr bequem.

Ja, ist ärgerlich: die Funktionalität kann man sehr gut gebrauchen. 
Leider hängen so viele Fallstricke dran, dass man besser die Finger von 
läßt.

Z.B. das Parsen von Kommandozeilen: man hat den Parser fertig und er 
scheint zu funktionieren. Bis man die Funktion mit einem Konstantstring 
(im .rodata) aufruft - Peng.  Dann splittet man z.B. bei Leerstellen und 
übergibt das abgetrennte Wort an eine Funktion - wollen wir mal hoffen, 
dass die nicht in ihren Tiefen auch irgendwo strtok benutzt denn sonst 
fängt meine Schleife an zu spinnen.

Ich bleib dabei: besser nicht benutzen.

von Folio (Gast)


Lesenswert?

Okay, jetzt müsst ihr euch doch nicht meinetwegen über Bibliotheken und 
deren Funktionen streiten! Ich dachte nur, das ich darauf so gut es eben 
geht verzichten sollte, wenn es für eine µC gerichtetes Programm werden 
soll. (Ich hatte noch im Hinterkopf: ein mal printf und das Flash ist 
voll ODER: Es ist nicht implementiert).

Darum habe ich auch ein eigenes atoi implementiert... aber egal, jetzt 
war es halt nur eine Übung für mich ;)

Wie würdet ihr das denn sonst machen?
Wie gesagt, ich bin noch absoluter Neuling und noch ordentlich grün 
hinter den Ohren. Mein momentanes Ziel ist es aktuell nur ein Frame aus 
4 Zahlen und ein paar Trenn/Sonderzeichen zu übertragen.

Hat vielleicht sonst jemand ein minimales Beispiel für mich, an dem ich 
mich orientieren kann?

Aja, falls von belangen: Ich verwende aktuell so ein spottgünstiges 
STM32-Arduino Clone ding (für unter 2€!) und ja auch den Arduino 
kompatiblen Bootloader von  Roger Clark.
Ob ich aber später das Arduinozeugs weiter benutzen will ist noch nicht 
sicher! Aktuell benutze ich es nur aus Bequemlichkeit!

von Peter D. (peda)


Lesenswert?

Folio schrieb:
> (Ich hatte noch im Hinterkopf: ein mal printf und das Flash ist
> voll ODER: Es ist nicht implementiert).

Nun, seit 1980 hat sich aber einiges getan. Die Compiler habe sich 
deutlich weiterentwickelt und auch die MCs haben erheblich mehr Flash 
und RAM.
Ein printf, scanf oder float bringt kaum noch einen MC zum Schwitzen.

Heutzutage legt man mehr Wert darauf, daß Programme schnell entwickelt 
werden können und auch zuverlässig, gut lesbar, wartbar und erweiterbar 
sind. Daher ist es besser, wenn man erprobte Libs verwendet, anstatt das 
Fahrrad immer wieder neu zu erfinden.

von W.S. (Gast)


Lesenswert?

Folio schrieb:
> Wie würdet ihr das denn sonst machen?
> Wie gesagt, ich bin noch absoluter Neuling und noch ordentlich grün
> hinter den Ohren. Mein momentanes Ziel ist es aktuell nur ein Frame aus
> 4 Zahlen und ein paar Trenn/Sonderzeichen zu übertragen.

Also ich kann Herangehensweisen nicht leiden, die von festen Positionen 
für Textzeichen ausgehen, so wie du das gemacht hast. So etwas ist sehr 
steif und versagt völlig, wenn da irgendwo mal das Format nicht ganz 
exakt stimmt. Es wäre ja in deinem Beispiel auch logisch nicht zu 
verstehen, wenn man für schwarz unbedingt
*000,000,000,000#
schreiben müßte. Weitaus bequemer ist es, wenn die einzelnen Einträge 
formatfrei gehalten wären, also *0 127 24 3# anstelle *000,127,024,003#, 
dann hättest du erstens weniger Aufwand beim Erzeugen der Zeilen und 
zweitens eine Flexibilität, die ein späteres Ändern viel leichter macht.

Mein Rat mit der Lernbetty war übrigens sehr ernst gemeint. Dort sind 
Input-Konvertierungen drin, die mit formatfreien Zeilen zurechtkommen. 
Warum? Weil man ja auch damit rechnen muß, daß man manuell eingetippte 
Kommandozeilen auszuwerten hat. Wer da auf feste Zahlenpositionen setzt, 
macht sich oder anderen Streß.

Beim cmd.c in der Lernbetty sähe das so aus:
char* Cpt;
Cpt = Pzeile;
if (match("*",&Cpt))
{ rot   = Long_In(&Cpt);
  gruen = Long_In(&Cpt);
  blau  = Long_In(&Cpt);
  W     = Long_In(&Cpt);
  if (!match("#",&Cpt)) SagFormatfehler();
}
Allerdings erinnere ich mich dunkel, daß abschließende Trennzeichen bei 
Zahleneingaben nicht überlesen werden, sondern ggf. getrennt abgefragt 
oder übergangen werden müßten - außer Leerzeichen. Naja, und ob du nun 
für deine Zwecke ein Long_In(..) brauchst oder mit einer geringeren 
Datenbreite auskommst, ist dein Geschmack. Bei 32 Bit Controllern wie 
der Lernbetty ist aber long quasi das natürlichste.



Ein ganz anderes Verfahren benutze ich, wenn es wirklich NUR um serielle 
Kommunikation zwischen 2 Maschinen geht. Die brauchen keine visuelle 
Rückmeldungen und keine Zeilen. Also ist es dort am besten, nur atomare 
Daten zu übertragen - dies aber wieder formatfrei.

Auf dein Beispiel bezogen sähe sowas dann so aus:

0R127G24B2W1K
..und so weiter. Ohne Leerzeichen (die würden überlesen) und ohne CRLF, 
das würde auch überlesen. Maschinen brauchen es nicht leserlich, sondern 
nur sparsam und leicht zu dekodieren. Jede Einstellung wird mit dem 
numerischen Wert begonnen und durch einen entsprechenden Buchstaben 
abgeschlossen, der den Wert übernimmt und dann den Akku für das 
Auflaufen des nächsten Wertes löscht.

und die Interpretation ist:
0R   Rotwert=0 setzen
127G Grünwert=127 setzen
24B  Blauwert setzen
2W   W_wert =2 setzen
1K   Konstantstromquelle 1 auf die gesetzten Werte einstellen

Und ob man da nun 0R oder bloß R oder 000000000000R schreibt, ist egal.
Das Angenehme bei diesem Verfahren ist, daß man praktisch keine 
Eingabezeile aufbauen muß. Man kann für den Dekoder einfach die Zeichen 
nehmen, so wie sie von der Seriellen herkommen. Das ist ne andere Nummer 
als das, was du machst:

Folio schrieb:
> Ich sehe mir also das Ende und den Start an und gucke ob die Kommas an
> der richtigen stelle stehen. Das klappt auch!
> Allerdings schaffe ich es nicht, die drei Zahlen aus dem String richtig
> per "atoi" zu extrahieren.

eben. Zuerst mußt du die Zeile aufbauen, um alles an festgelegte 
Positionen zu bringen und dann klappt dein atoi trotzdem nicht.

W.S.

von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> Heutzutage legt man mehr Wert darauf, daß Programme schnell entwickelt
> werden können und auch zuverlässig, gut lesbar, wartbar und erweiterbar
> sind. Daher ist es besser, wenn man erprobte Libs verwendet, anstatt das
> Fahrrad immer wieder neu zu erfinden.

Nö. So etwas würde nur dann wirklich Sinn machen, wenn die genannten 
"erprobten" Libs auch wirklich zum angezielten Einsatzzweck passen. Daß 
das in dem vorliegenden Falle eher nicht stimmt, hast du ja hier lesen 
können. Abgesehen davon ist auch das Exposé des TO nicht wirklich 
zweckmäßig.

Wichtig ist nur eines: daß man als Mikrocontroller-Programmierer mit der 
Zeit sich ein Portfolio an hardwareunabhängigen Modul zulegt, die auf 
die Bedingungen des µC-Bereiches passen. Und dazu zählt printf und 
Konsorten eben nicht, weil das eben immer einen Formatzeilen-Interpreter 
bedeutet, der zur Laufzeit Dinge tut, die man eigentlich bereits zur 
Compilationszeit erledigt haben könnte. Sowas ist nur der bellenden 
Faulheit und gedankenlosem Umgang mit den Ressourcen geschuldet. 
Abgesehen davon muß man bei printf auch darauf achten, was für Ausdrücke 
die betreffende Variante überhaupt kennt. Die Varianten für µC sind 
oftmals gekürzte Implementationen, weil man hier mehr Wert darauf legt, 
das Ganze in den Flash zu kriegen.

W.S.

von Joachim B. (jar)


Lesenswert?

W.S. schrieb:
> Also ich kann Herangehensweisen nicht leiden, die von festen Positionen
> für Textzeichen ausgehen, so wie du das gemacht hast.

auch wenn ich deine Sichtweise anerkenne, für den Anfang hatte der TO 
doch gut überlegt,

wenn schon die Zeile parsen doch dann auf das erste Vorkommen einer 
Ziffer (ggffs. erweitert um +-) *1.) als Start Pointer zu Atoi.

Dann die Länge feststellen, also weiterparsen bis keine Ziffer mehr 
kommt und wenn dann noch nicht \0 oder Ende vom String erreicht ist 
weitersuchen, bis zum nächsten Vorkommen von *1.)

solange bis alle Zahlen gefunden sind, der String beendet ist.

Ist eine schöne Lernübung.

von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> char* Cpt;
> Cpt = Pzeile;
> if (match("*",&Cpt))
> { rot   = Long_In(&Cpt);
>   gruen = Long_In(&Cpt);
>   blau  = Long_In(&Cpt);
>   W     = Long_In(&Cpt);
>   if (!match("#",&Cpt)) SagFormatfehler();
> }

Es ist weitgehend sinnfrei, Beispiele zu posten, die weder Standardlibs 
verwenden, noch den Quelltext der Funktionen beinhalten.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Peter D. schrieb:
> noch den Quelltext der Funktionen beinhalten.

Aber das verwendet doch die "Lernbetty"!
Das ist quasi der C-Standardcode überhaupt, auch wenn die ISO den 
immer noch nicht abgesegnet hat.

von Kaj (Gast)


Lesenswert?

Ob das jetzt gut oder schlecht ist, wie die Nachricht des TO aufgebaut 
ist, sei mal dahingestellt. Ohne strtok, atoi & co. koennte das 
vielleicht so aussehen (Fehlerbehandlung hab ich mal weggelassen):
1
#include <stdint.h>
2
#include <stdio.h>
3
#include <inttypes.h>
4
5
6
uint16_t ascii_to_u16(char* str);
7
void parse(char* str, uint16_t* buff);
8
9
10
int main(void)
11
{
12
    char msg[] = "*127,255,001,111#";
13
    uint16_t nums[4] = {0};
14
15
    parse(msg, nums);
16
17
    for (int i = 0; i < 4; i++) {
18
        printf("Num[%i]: %" PRIu16 "\n", i, nums[i]);
19
    }
20
21
    return 0;
22
}
23
24
25
uint16_t ascii_to_u16(char* str)
26
{
27
    uint16_t ret_val = (str[0] - '0') * 100 + (str[1] - '0') * 10 + (str[2] - '0');
28
29
    return ret_val;
30
}
31
32
33
void parse(char* str, uint16_t* buff)
34
{
35
    buff[0] = ascii_to_u16(&str[1]);
36
    buff[1] = ascii_to_u16(&str[5]);
37
    buff[2] = ascii_to_u16(&str[9]);
38
    buff[3] = ascii_to_u16(&str[13]);
39
}
1
$ gcc -std=c99 -Wall -Wextra -o main main.c && ./main
2
Num[0]: 127
3
Num[1]: 255
4
Num[2]: 1
5
Num[3]: 111
Das ist natuerlich nicht das optimum, da Fehlerbehandlung fehlt, 
negative Zahlen nicht beruecksichtigt werden, und die Zahl maximal 3 
Ziffern haben darf. Funktioniert also nur fuer 000 bis 999. Das koennte 
aber fuer den TO vielleicht trotzdem ein Anfang sein.

von W.S. (Gast)


Lesenswert?

Kaj schrieb:
> void parse(char* str, uint16_t* buff);

Tja, das ist zu wenig.
Dein parse kann keinen String parsen.
Stattdessen ist es nur eine Zusammenfassung von 4x ascii_to_u16(..), die 
mit jeweils einem festen Zeiger aufgerufen werden.

Eine parse-Funktion muß anders aussehen.
Etwa so:
my_int_typ parse(char** posinstring);

Das deshalb, weil parse ja den String durchsuchen soll, dabei sowas wie 
Leerzeichen übergehen, dann die Zahl konvertieren und anschließend den 
Zeiger im String auf die Stelle dahinter setzen. Sowas ist "parsen".

Peter D. schrieb:
> Es ist weitgehend sinnfrei,

Nee Peter, dein Beitrag war herzlich sinnlos, da du ja keinen positiven 
Beitrag leisten, sondern nur mäkeln wolltest.

Mein Beitrag war sehr sinnvoll, da er einen Eindruck von der 
Verwendung echt parsender Eingabe-Konvertierungen gibt. Genau DAS war 
das Anliegen. Du hättest es erkennen können, wenn du nur korrekt gelesen 
hättest.

Die Betonung liegt auf VERWENDUNG. Wie das modulintern geht, kann ja 
jeder nachlesen, die Quelle ist genannt.

Und Rufus liegt mit seiner überflüssigen Bemerkung mal wieder 
grundfalsch (wie so oft in letzter Zeit), denn meine Routinen in der 
Lernbetty sind im Gegensatz zu dem Standard-Zeugs, was bei den meisten 
C-Compilern so dabei ist, eben nicht Standard, sondern an die 
Verhältnisse in einem MIKROCONTROLLER angepaßt. In diesem Falle sogar 
hardwareunabhängig und dennoch effektiver als sowas wie atoi, denn das 
kann ja nicht einmal das Ende der Zahl im String zurückliefern.

Ich erinnere mich an die Bemerkungen eines echten Programmierer-Idioten 
vor langer Zeit, der sich darüber aufgeregt hat, daß man bei den kleinen 
PIC16 ja nur 4 Plätze im Stack hat - wo er doch bei seinem PC ja 
mindestens 16K oder noch mehr an Stack hätte, weshalb also diese Chips 
ein völlig ungeeignetes zu nix zu gebrauchendes Zeugs seien.

So ungefähr kommen mir Bemerkungen über Standard-Funktionen in C auf dem 
µC vor.

W.S.

von W.S. (Gast)


Lesenswert?

Nochwas:
War das nicht so, daß atoi als Argument ein "const char* string" haben 
will?
Was machen denn da die AVR-Anwender?

W.S.

von mh (Gast)


Lesenswert?

W.S. schrieb:
> Nochwas:
> War das nicht so, daß atoi als Argument ein "const char* string" haben
> will?
> Was machen denn da die AVR-Anwender?
>
> W.S.

Ja, atoi hat einen "const char*" Parameter. Wo siehst du da ein Problem? 
Bei all deinen "selbstbewussten" Beiträgen können wir doch sicher 
erwarten, dass du "const" verstanden hast.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

W.S. schrieb:
> War das nicht so, daß atoi als Argument ein "const char* string" haben
> will?

Erkundige Dich doch erstmal, was "const" in einem Funktionsparameter 
bedeutet.

> Was machen denn da die AVR-Anwender?

Dasselbe, was die anderen Anwender auch machen.

: Bearbeitet durch Moderator
von Peter D. (peda)


Lesenswert?

W.S. schrieb:
> Nö. So etwas würde nur dann wirklich Sinn machen, wenn die genannten
> "erprobten" Libs auch wirklich zum angezielten Einsatzzweck passen. Daß
> das in dem vorliegenden Falle eher nicht stimmt, hast du ja hier lesen
> können.

Warum soll man auf dem AVR kein sscanf verwenden dürfen?
Ich habs mal compiliert, das Programm ist ~2kB groß, paßt also bequem in 
einen kleinen ATtiny85.

Man muß heute nicht mehr den Stand von 1980 (P8751: 4kB OTP, 0,1kB RAM) 
als Maßstab nehmen.

von gfjgfjffjffjfjfjfjf (Gast)


Lesenswert?

*127,255,001,111#

Kaj schrieb:
> void parse(char* str, uint16_t* buff)
> {
>     buff[0] = ascii_to_u16(&str[1]);
>     buff[1] = ascii_to_u16(&str[5]);
>     buff[2] = ascii_to_u16(&str[9]);
>     buff[3] = ascii_to_u16(&str[13]);
> }

das klappt auch nur wenn die zahlen immer 3 byte lang sind
also *001,002,003,004#

ich würde vlt die trenner nullen
*127\0255\0001\0111\0
und dabei die pointer merken
genullt sind sie ja dann alle schon

von Marco H. (damarco)


Lesenswert?

foobar schrieb:
>>> strtok sollte man aus seinem Repertoir streichen!
> Ich bleib dabei: besser nicht benutzen.

Nun ja pauschal kann das nicht sagen...

Z.Bsp wenn man solche Funktionen die den String verändern nur eine Kopie 
übergibt. Bleibt nur noch der versteckte State..

Aber man solche Funktionen auch selbst sicher bauen...

von Peter D. (peda)


Lesenswert?

Wem das smarte strtok() zu "gefährlich" ist, der kann auch mit strchr() 
nach '*' bzw. ',' suchen lassen und dann atoi() den Pointer+1 übergeben.

Ich verwende sscanf() auch eher selten. Es ist recht unflexibel, wenn 
die Daten verschiedene Formate haben können.

von DPA (Gast)


Angehängte Dateien:

Lesenswert?

Man könnte mit einer Funktion wie dieser integer parsen:
1
bool nintp(const char**restrict s, const char* end, int*restrict state){
2
  const char* it = *s;
3
  int x = *state;
4
  for(;it < end && *it>='0' && *it<='9'; it++)
5
    x = x * 10 + (it[0] - '0');
6
  bool res = *s == it;
7
  *s = it;
8
  *state = x;
9
  return res;
10
}

Aber sobald man das in einer state-machine verwenden will, wird es sehr 
Komplex (beispiel siehe Anhang).

Ich mache meine Parser stattdessen gerne so, dass ich bei jedem 
Durchlauf immer maximal ein Zeichen einlese, und speichere dann einfach 
alle nötigen Zustände.

von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Anbei mal mit strchr + atoi.
Das komplette Programm belegt 296 Byte auf dem ATtiny85.
1
bool get_vals(char* buf, uint8_t* vals, uint8_t count)
2
{
3
  buf = strchr(buf, '*');
4
  for (; count; count--)
5
  {
6
    if (buf == NULL)
7
      return false;             // no success
8
    *vals++ = atoi(++buf);      // read number after delimiter
9
    buf = strchr(buf, ',');     // point to next delimiter
10
  }
11
  return true;                  // wanted count of numbers read
12
}

Ups, da war noch ein Fehler drin, jetzt sollte es stimmen.

[Mod: altes Attachment gelöscht]

: Bearbeitet durch Moderator
von Arc N. (arc)


Lesenswert?

Mal das Ausgangsbeispiel (*127,255,001,111#) in EBNF/Wirth-Notation 
übertragen mit der Annahme, dass immer alle vier Werte vorhanden sein 
müssen:

cmd_line := start unsigned comma unsigned comma unsigned comma unsigned 
end
unsigned := digit { digit }
digit := '0' .. '9'
start := '*'
end := '#'
comma := ','

Zur Notation:
irgendwas = muss (an der Stelle) vorhanden sein
[ irgendwas ] = kann 0 oder 1 mal vorhanden sein
{ irgendwas } = kann 0 oder n mal vorhanden sein
'irgendwas' = Terminal, muss dort so vorhanden sein

Damit lässt sich dann ein simpler Scanner/"Parser" basteln (auch wenn 
der hier mMn etwas Overkill ist):
1
// Nur als grober Ansatz
2
// Nicht alle möglichen Fehler werden abgefangen 
3
// Noch wurde das ausgiebig getestet 
4
enum TokenType { START, END, COMMA, UNSIGNED } ;
5
typedef struct {
6
  TokenType Type;
7
  unsigned int Value;
8
} Token;
9
10
static bool parse(const Token* tokens, size_t num_tokens) {
11
  if (!tokens) return false;
12
  if (tokens[0].Type != START) return false;
13
  if (tokens[1].Type != UNSIGNED) return false;
14
  if (tokens[2].Type != COMMA) return false;
15
  if (tokens[3].Type != UNSIGNED) return false;
16
  if (tokens[4].Type != COMMA) return false;
17
  if (tokens[5].Type != UNSIGNED) return false;
18
  if (tokens[6].Type != COMMA) return false;
19
  if (tokens[7].Type != UNSIGNED) return false;
20
  if (tokens[8].Type != END) return false;
21
  return true;
22
}
23
24
static bool scan(const char* input, size_t input_len, Token* tokens, size_t max_num_tokens) {
25
  size_t index = 0;
26
  size_t num_tokens = 0;
27
  if (!input || !tokens) return false;
28
  while (index < input_len) {
29
    if (num_tokens >= max_num_tokens) break;
30
    if (iswspace(input[index])) {
31
      index++;
32
      continue;
33
    }
34
    if (input[index] == '*') {
35
      tokens[num_tokens].Type = START;
36
      tokens[num_tokens].Value = 0;
37
      num_tokens++;
38
      index++;
39
      continue;
40
    }
41
    if (input[index] == '#') {
42
      tokens[num_tokens].Type = END;
43
      tokens[num_tokens].Value = 0;
44
      num_tokens++;
45
      index++;
46
      continue;
47
    }
48
49
    if (input[index] == ',') {
50
      tokens[num_tokens].Type = COMMA;
51
      tokens[num_tokens].Value = 0;
52
      num_tokens++;
53
      index++;
54
      continue;
55
    }
56
57
    if (isdigit(input[index])) {
58
      unsigned int value = 0;
59
      // insert overflow checks...
60
      while (index < input_len) {
61
        if (!isdigit(input[index])) break;
62
        value *= 10;
63
        value += (input[index] - '0');
64
        index++;
65
      }
66
      tokens[num_tokens].Type = UNSIGNED;
67
      tokens[num_tokens].Value = value;
68
      num_tokens++;
69
      continue;
70
    }
71
    // anything else = error
72
    return false;
73
  }
74
  return true;
75
}
76
77
// Aufruf dann bspw.: 
78
char* instr = "*127,255,001,111#";
79
Token tokens[20];
80
bool scan_res = scan(instr, strlen(instr), tokens, 20);
81
bool parse_res = parse(tokens, 20);

von S. R. (svenska)


Lesenswert?

Für solche Aufgaben sind regex doch wie geschaffen. Auf AVR habe ich die 
bisher nicht benutzt, sind die in der avr-libc implementiert?

Eine kleine Implementation (2 KB Code) liegt auf Github rum: 
https://github.com/kokke/tiny-regex-c

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.