Hallo Fange gerade an mit C zu spielen und bin gleich mal über folgendes Problem gestolpert: Wenn ich mit strtok(String, delimiter); folgenden String zerteilen möchte "$PFLAU,0,1,1,1,0,,0,,*63"; stolpert die Funktion über den leeren Token vor "*63". Die anderen Felder werden richtig getrennt. Gibt es da eine bessere Möglichkeit, solche Strings zu teilen ? Muss ich diese Fälle von Hand abfangen ? Danke Torsten
die frage ist strtok stolperst oder du - aber ohne code ist das schwer zu sagen.
wollte die Welt nicht mit Anfänger Code langweilen, aber hier ... Das struct wird bis zum ersten "leeren" Token erwartungsgemäß mit den Daten gefüttert. Die leeren werden dann übersprungen.
Das ist normal. strsep ist dasselbe wie strtok ohne den Bug.
Ist kein Bug, sondern Feature. Die Entwickler haben wahrscheinlich hauptsächlich an Whitespace als Delimiter gedacht. Und bei fast allem, was man parsen kann (z.B. C), ist ja "int count" äquivalent zu "int count".
Es ist ein Bug. Auch bei Whitespace passiert dir dasselbe, daß man eine Null bei zwei aufeinanderfolgenden Spaces zurückbekommt und infolge dessen die Zeile nicht parsen kann einfach weil ein Feld leer ist. HISTORY The strsep() function is intended as a replacement for the strtok() func- tion. While the strtok() function should be preferred for portability reasons (it conforms to ANSI X3.159-1989 (``ANSI C89'')) it is unable to handle empty fields, i.e., detect fields delimited by two adjacent delim- iter characters, or to be used for more than a single string at a time.
Aha, danke Wird die Funktion auf die gleich Art und Weise genutzt und kennt sie die aktuelle string.h auch ? Hab gerade mal im Quelltext einfach die "strtok" durch "strsep" ersetzt und der Simulator im Studio 4.18 hat sich gleich mal aufgehängt. Was muss man da beachten ? Danke Torsten
Nein, leicht unterschiedlich. Bei strsep musst du ein Pointer auf einen String übergeben, also mit dem & operator. Hier die Definition: char * strsep (char **, const char *) char * strtok (char *, const char *) Beispiel: for(i=0,str=line;str&&s=strsep(&str," \t");i++) Wenn str als Funktionsargument übergeben wird, und du es später nicht mehr brauchst, dann kannst du es auch direkt benutzen und brauchst keine temporaren Stringpointer, da der ja von der Funktion verändert wird und zum Schluss Null zugewiesen bekommt. Hier wird einfach auf die Gültigkeit von str geprüft, da das ja von einer vorherigen Funktion auf null gesetzt werden kann, es ist immer gut sowas abzufangen, bei generischem code.
Chris schrieb: > Es ist ein Bug. Naja, als Bug würde ich das nicht bezeichnen, denn die Funktion verhält sich genau wie im C99-Standard spezifiziert:
1 | A sequence of calls to the strtok function breaks the string pointed |
2 | to by s1 into a sequence of tokens, each of which is delimited by a |
3 | character from the string pointed to by s2. The first call in the |
4 | sequence has a non-null first argument; subsequent calls in the |
5 | sequence have a null first argument. The separator string pointed to |
6 | by s2 may be different from call to call. |
7 | |
8 | The first call in the sequence searches the string pointed to by s1 |
9 | for the first character that is not contained in the current separator |
10 | string pointed to by s2. If no such character is found, then there are |
11 | no tokens in the string pointed to by s1 and the strtok function |
12 | returns a null pointer. If such a character is found, it is the start |
13 | of the first token. |
14 | |
15 | The strtok function then searches from there for a character that is |
16 | contained in the current separator string. If no such character is |
17 | found, the current token extends to the end of the string pointed to |
18 | by s1, and subsequent searches for a token will return a null pointer. |
19 | If such a character is found, it is overwritten by a null character, |
20 | which terminates the current token. The strtok function saves a |
21 | pointer to the following character, from which the next search for a |
22 | token will start. |
23 | |
24 | Each subsequent call, with a null pointer as the value of the first |
25 | argument, starts searching from the saved pointer and behaves as |
26 | described above. |
27 | |
28 | The implementation shall behave as if no library function calls the |
29 | strtok function. |
Allein schon der Funktionsname (ausgeschrieben "string tokenize") impliziert, dass keine Leerstrings als Resultat geliefert werden, da Tokens im üblichen Computerjargon immer nichtleere Zeichenketten sind. Oft ist dieses Verhalten von strtok tatsächlich erwünscht (s. Beitrag vom mechatroniker). Wenn nicht, muss man eben strsep als Ersatz nehmen (sofern verfügbar, denn strsep ist nicht im C99-Standard), oder selber etwas zusammenbasteln.
Danke für die Auskünfte, aber wenn ich ehrlich bin, habe ich noch nicht verstanden, wie ich die Funktionen nun in meinem Fall austauschen kann. in halt nicht sehr erfahren .. Hat noch einmal jemand die Muse, mir das anfängertauglich zu erklären ? Danke Torsten
Yalu X. schrieb: > A sequence of calls to the strtok function breaks the string pointed > to by s1 into a sequence of tokens, each of which is delimited by a > character from the string pointed to by s2. The first call in the > sequence has a non-null first argument; subsequent calls in the > sequence have a null first argument. The separator string pointed to > by s2 may be different from call to call. wobei das schon ziemlich starker Tobak ist. von Reentrance keine Spur
Vlad Tepesch schrieb: > wobei das schon ziemlich starker Tobak ist. > von Reentrance keine Spur na ja, als das designt wurde hat man in Unix eigentlich kein (oder zumindest kaum) Multithreading sondern nur Multitasking gemacht. Und das bedeutete, daß jeder Prozess seinen eigenen Adressraum hatte, also sein eigenes strtok. Insofern gab es da kein Problem.
Möchte die Expertendiskussion ja nicht weiter stören, aber ich habe es jetzt mal so geschrieben und ich dachte, dass es funktioniertals keine leeren Token in dem String waren. Wenn ich jedoch eine Zahl aus dem String lösche und damit einen leeren Token erzeuge, werden von dem ersten Token schon die ersten 2 Zeichen nicht richtig übertragen und alle weiteren Zeichen sind entsprechend verschoben. Kann jemand helfen ? Danke Torsten
bin warscheinlich schon zu alt, aber bei der Ansi Normierung gab es intendet usage for strtok und die gingen nicht (c89), klar daß man die dann bei der nächsten Revision berichtigt hat. Das war alles noch vor Google und somit heutzutage nicht mehr auffindbar, und meine Emails von damals habe ich auch gelöscht. Zum eigentlichen Thema: org source: ptr = strtok(Sentence,delimiter); strcpy(S_DATA.Name,ptr); while(ptr != NULL) { ptr = strtok(NULL, delimiter); new source: es braucht zusätzlich eine neue variable "char*str" als Beispiel strcpy(S_DATA.Name,str=Sentence); while(ptr=strsep(&str,delimiter)) { Noch zwei Anmerkungen Es kann sein, daß du ein buffer-overflow produzierst, da die Null-Terminierung des Strings überschrieben werden kann. Das einfachste in deinem Code wäre sizeof(Sequence)-1 zu schreiben. Normalerweise löst man das so, daß man die betreffende Variable als static declariert und ein Define mit SENTENCE_LEN oder so macht, sowie bei der declaration Sentence[SENTENCE_LEN+1] macht. Wenn man es nicht als static declariert, dann inizialisiert man das Feld mit NULL, es gibt aber auch noch andere Denkschulen, das soll hier nur als eine Möglichkeit gesehen werden. Wengen Multitrhreading, es gibt auch strtok_s sowie strtok_e . Dasselbe gibt es für strsep. Die korrekte Implementierung von strtok/strsep laut ansi-c (Annex K) hat sich leider nicht durchgesetzt.
sorry, war unterbrochen worden, die strcpy(S_DATA.Name,str=Sentence); stimmt nicht, so wie du es gemacht hast ist richtig.
Hi Chris Danke für Deine Antwort! wie gesagt... für einen String: $PFLAU,1,2,3,4,5,6,7,8,*63 klappt alles wunderbar --> S_DATA.Name == '$' , 'P' , 'F' , 'L' , 'A' , 'U' S_DATA.RX == '1' S_DATA.TX == '2' .... wenn der String jedoch so aussieht: $PFLAU,,2,3,4,5,6,7,8,*63 kommt es zu S_DATA.Name == 'F' , 'L' , 'A' , 'U' , '' , '2' S_DATA.RX == '3' S_DATA.TX == '4' .... hast Du dafür eine Erklärung ? Wie geht denn der Profi mit Leeren Feldern in RS232 Datensätzen um ? Es kann doch nicht sein, dass ich der erste Mensch bin, der das machen muss.
Torsten B. schrieb: > Gibt es da eine bessere Möglichkeit, solche Strings zu teilen ? > Muss ich diese Fälle von Hand abfangen ? Torsten B. schrieb: > Wie geht denn der Profi mit Leeren Feldern in RS232 Datensätzen um ? > Es kann doch nicht sein, dass ich der erste Mensch bin, der das machen > muss Solches Parsing (gerade wenn es komplexer wird) kann man mit Zustandsmaschinen machen. Da muss man natürlich mehr selbst implementieren, allerdings ist dieser Ansatz dem "händischen Stringzerpflücken" weit überlegen und übersichtlicher. Man geht dabei grob gesagt so vor, dass man zyklisch je ein Zeichen aus dem Zeichenstrom entnimmt und der Zustandsmaschine zuführt. Abhängig vom Zeichen werden dann Aktionen und Zustandsübergänge ausgelöst.
kurz wieder hier, Nein, habe keine Erklärung, auch das Fehlen des `$` ist unerklärlich. Ich nehme an, der Code ist der den du zuletzt gepostet hast. Abgesehen davon, daß der Code unbrauchbar ist, aber das hat jetzt nichts zur Sache. Punkt eins, ändere folgendes, was ich schon sagte: while(ptr != NULL) { ptr = strsep(&running,delimiter); => while(ptr = strsep(&running,delimiter)) { printf("%d:<%s>\t<%s>\n",i,ptr,running?:"(NULL)"); Dann kannst du nachvollziehen, was passiert. PS, bei der AVR-Lib gibt es einen bug bezüglich strsep, es könnte auch sein, daß das damit was zu tun hat. Ansonsten würde man sowas anders machen, aber dazu eventuell später.
Chris schrieb: > PS, bei der AVR-Lib gibt es einen bug bezüglich strsep Falls du damit die avr-libc meinst: welchen bitte? Wo ist der Bugreport?
Der ist alt, von 2004, [bugs #10078] Habe deine Datei getestet, funktioniert einwandfrei (auf dem PC). Als output habe ich folgendes: $PFLAU,1,89,3,4,5,6,7,8,*63 1:<$PFLAU> <1,89,3,4,5,6,7,8,*63> 2:<1> <89,3,4,5,6,7,8,*63> 3:<89> <3,4,5,6,7,8,*63> 4:<3> <4,5,6,7,8,*63> 5:<4> <5,6,7,8,*63> 6:<5> <6,7,8,*63> 7:<6> <7,8,*63> 8:<7> <8,*63> 9:<8> <*63> 10:<*63> <(null)> $PFLAU,,89,3,4,5,6,7,8,*63 1:<$PFLAU> <,89,3,4,5,6,7,8,*63> 2:<> <89,3,4,5,6,7,8,*63> 3:<89> <3,4,5,6,7,8,*63> 4:<3> <4,5,6,7,8,*63> 5:<4> <5,6,7,8,*63> 6:<5> <6,7,8,*63> 7:<6> <7,8,*63> 8:<7> <8,*63> 9:<8> <*63> 10:<*63> <(null)>
Und hier noch einer mit zwei fehlenden Feldern, $PFLAU,,,3,4,5,6,7,8,*63 1:<$PFLAU> <,,3,4,5,6,7,8,*63> 2:<> <,3,4,5,6,7,8,*63> 3:<> <3,4,5,6,7,8,*63> 4:<3> <4,5,6,7,8,*63> 5:<4> <5,6,7,8,*63> 6:<5> <6,7,8,*63> 7:<6> <7,8,*63> 8:<7> <8,*63> 9:<8> <*63> 10:<*63> <(null)> Wie man normalerweise solche Felder parst, gleich mit strtol , das hier ist einfach, aber es geht auch so. Andere Anmerkungen. Normalerweise würde ich den String direkt nehmen, ohne strdup. Als Alternative einen buffer nehmen, indem du die Sequenz reinkopierst, aber Sequence dürfte bereits ein buffer sein, nehme mal an strdup ist nur jetzt vorhanden und wird später eliminiert. Du solltest das Format auf gültigkeit prüfen, wie z.B if (*running!='$') running=strchr(running,'$'); // ein simpler Vorschlag for(i=0,ptr=running;ptr&&*ptr&&*++ptr!='*';ptr++) i^=*++ptr; if(ptr&&*ptr=='*') i^=strtol(ptr+1,&ptr,16); else i|=0x100; if(i) nema_error++; /*auch nur ein simpler Vorschlag*/ else /*parse nema*/ Was aber der Grund ist, daß der Code nicht funktioniert ist folgender: Dein Struct. Ein Beispiel. char RX[1]; char TX[1]; char GPS[1]; Da passt nur der Nullterminator von strcpy rein, und natürlich erzeugst du buffer overflow, strncpy wäre ein nicht ernst gemeinter Vorschlag. Normalerweise würde man aber eher die Daten parsen mittels strtol oder equivalent bzw mit einer eigenen Funktion welche auch 'N', 'W' usw entsprechend einliest und in eine Zahl (Enum) umwandelt.
Das hier ist natürlich bullshit. for(i=0,ptr=running;ptr&&*ptr&&*++ptr!='*';ptr++) i^=*++ptr es sollte sowas sein: for(i=0,ptr=running;ptr&&*ptr&&*++ptr!='*';i^=*ptr);
Chris schrieb: > Der ist alt, von 2004, [bugs #10078] Ach, das ist ja hornalt. Meinst du wirklich, dass noch jemand mit einer avr-libc < 1.0 arbeitet? ;-)
> Ach, das ist ja hornalt. Meinst du wirklich, dass noch jemand mit > einer avr-libc < 1.0 arbeitet? ;-) Nein. Das hatte ich erst nachher rausgefunden, daß der so alt war. Laut meinem drüberschauen dürfte es den Fehler nicht haben, deshalt hattei ich auch den Code ausprobiert und bei mir lief er einwandfrei, auch wenn nur am PC.
Hallo zusammen zunächst erst mal vielen Dank für Eure Hilfe und besonders an Chris für seine ausführlichen Beiträge: Chris schrieb: > while(ptr = strsep(&running,delimiter)) > { > printf("%d:<%s>\t<%s>\n",i,ptr,running?:"(NULL)"); > > Dann kannst du nachvollziehen, was passiert. Wahrscheinlich liegt es an meiner Unerfahrenheit, aber wieso kann ich das jetzt besser nachvollziehen ? Ich fange gerade an, mich mit C zu beschäftigen und habe vorher nur mit Assembler gebastelt. Was macht die printf-Zeile hier ? Chris schrieb: > Der ist alt, von 2004, [bugs #10078] > Habe deine Datei getestet, funktioniert einwandfrei (auf dem PC). > Als output habe ich folgendes: Hast Du den Code auch im AVR Studio getestet ? Ich habe die Version 4.18 Was mich hierbei am meisten verwundert, ist die Tatsache, dass ich bei einer ersten Compilierung (STRG+F7) 3 Warnings bekomme und bei einer zweiten Compilierung (1 Sekunde später, ohne Änderungen am Quellcode) keine Warnings mehr bekomme. Ist das ein Bug im Studio, oder lieg das Problem hier auch zwischen meinen Ohren ? Chris schrieb: > Normalerweise würde ich den String direkt nehmen, ohne strdup. > Als Alternative einen buffer nehmen, indem du die Sequenz reinkopierst, > aber Sequence dürfte bereits ein buffer sein, nehme mal an strdup ist > nur > jetzt vorhanden und wird später eliminiert. Das mit strdup habe ich aus einer reference zu strdup im Internet. Habe versucht, es ohne zu machen, aber dann hat gar nichts mehr funktioniert. Chris schrieb: > Du solltest das Format auf gültigkeit prüfen, wie z.B > if (*running!='$') running=strchr(running,'$'); // ein simpler Vorschlag > for(i=0,ptr=running;ptr&&*ptr&&*++ptr!='*';ptr++) i^=*++ptr; > if(ptr&&*ptr=='*') i^=strtol(ptr+1,&ptr,16); else i|=0x100; > if(i) nema_error++; /*auch nur ein simpler Vorschlag*/ else /*parse > nema*/ OK Leider habe ich keine Ahnung, was das bedueten könnte. Ich werde mich wahrscheinlich erst mal ein paar Jahre mit einem C Buch im Wald verstecken müssen, bi ich das drauf habe ;-) Bin im Augenblick auf Dienstreise und kann daher den Code auch nicht auf einem Prozessor ausprobieren. Ich wollte dieses Projekt eigentlich für den Einstieg nehmen. Vielleicht habe ich mich übernommen. Wenn jamand die Muse und die Zeit hat, mir die printf und die kryptische for-Schleife zu erklären, wäre das toll, aber ich erwarte es nicht wirklich. :-) Danke für Eure Hilfe Torsten
Das printf generiert (printed) dir den Output raus, wenn du hapsim oder equivalent installiert hast, dann bekommst du den output wie oben gepostet. Und nein, ich benutze AVR Studio nicht, deshalb kann ich dir nicht sagen ob es damit läuft, auf simavr geht es zumindest. Printf gibt Daten auf die Serielle oder dem LCD Display aus. In simavr wird es auf der Console ausgegeben. > printf("%d:<%s>\t<%s>\n",i,ptr,running?:"(NULL)"); Das obige printf gibt den Inhalt der Variablen i,ptr,running aus und gleichzeitig fängt es den Fall ab, wenn running Null ist, dann zeigt es das an. Das ?: ist eine GCC erweiterung und kein portables C, ansonsten müsste es running?running:"(NULL)" heissen, in diesem Falle. Die For Schleife ist dasselbe wie deine do mit den Zeilen zuvor/danach. Die wurde hauptsächlich gemacht, wegen des mehrmals erwähnten Falles, dass beim Code von dir ein Nullpointer sein könnte. Z.B. bei diesem $PGRMIE*56 oder auch nur einer simplen leeren Zeile oder abgebrochenen Message. dann würde bei deinem Code zwar die while Schleife durchlaufen werden aber ptr würde den Wert 0 haben. > OK > Leider habe ich keine Ahnung, was das bedueten könnte. Ich werde mich >> if (*running!='$') running=strchr(running,'$'); // ein simpler Vorschlag Die GPS daten beginnen alle mit dem Dollarzeichen, und wenn am Anfang kein Dollarzeichen ist, dann überspringe alles bis zu einem Dollarzeichen. Weiters haben GPS Daten eine sogenannte Checksum, um die Richtigkeit der Daten zu überprüfen. Auch die sollte zuerst überprüft werden bevor mit dem Datensatz gearbeitet wird. Dieses for macht ein Xor über alle zeichen bis zum Checksum Trennzeichen, wie es in dem NEMA Standard definiert ist. >> for(i=0,ptr=running;ptr&&*ptr&&*++ptr!='*';i^=*ptr); Als Resultat enthält i jezt das Resultat aller Zeichen zwischen '$' und '*' welche mittels Xor Operator miteinander aufsummiert wurden, wenn man das so nennen kann. >> if(ptr&&*ptr=='*') i^=strtol(ptr+1,&ptr,16); else i|=0x100; Hier wird zuerst geprüft ob der Checksumoperator vorhanden ist, wenn nicht wird zur Checksum der Wert 0x100 aufsummiert, sodaß das Resultat auch erhalten bleibt, für eine eventuelle Fehleranalyse. Sollte jedoch eine Checksum vorhanden sein, dann wird sie eingelesen, mittels Xor mit i verknüpft und der ptr (string) wird zum Ende der Checksum aktualisiert. Wenn die Checksum richtig ist, enthält i Null, ansonsten einen Wert ungleich Null, was dann in der nächsten Zeile auch verwendet wird, um dann ev. die Nema-Zeile zu parsen. >> if(i) nema_error++; /*auch nur ein simpler Vorschlag*/ else /*parse >> nema*/ > > wahrscheinlich erst mal ein paar Jahre mit einem C Buch im Wald > verstecken müssen, bi ich das drauf habe ;-)
Öhh, danke !!!! Ich gehe dann mal meine Hausaufgaben machen und melde mich wieder, wenn ich weitergekommen bin, bzw. vor dem nächsten Berg stehe. Hoffe, Du hast dann auch noch so viel Zeit für mich. Danke und Gruß Torsten
Hier ein Auszug aus meinem GPS parsing, ist aber sehr Zeitoptimiert und speziell , keine generisches parsing. #define nmea2i(x) (nmea_sep(msg,1),strtol(x,&x,10)) #define nmea_valid(msg,x) (nmea_sep(msg,x)?nmea_skip(strchr("AD",*msg)?1:0):1) if ((*(uint16_t*)msg=='GP'&&(msg+=2)) { // GPS message if(*(uint32_t*)msg=='RMC,'&&(msg+=3)) { // RMC Recommended Minimum sentence C nmea2i(msg); // fix taken at utc if(!nmea_valid(msg,1)) return NEMA_INVALID; a=nmea2lat(msg); // lat b=nmea2lat(msg); // lon c=fixmul(nmea2f(msg),ktof(0,514444444)); // speed [m/s] d=nmea2f(msg); // true heading nmea2i(msg); // date e=nmea2lat(msg); // magnetic variation if(!nmea_valid(msg,0)) return NEMA_INVALID; gps[old]->lat=a; gps[old]->lon=b; gps[old]->spd=c; gps[old]->thd=d; gps[old]->mag=e; return NEMA_UPDATE; } else if(*(uint32_t*)msg=='GGA,'&&(msg+=3)) { // GGA Global Position Fix Data nmea2i(msg); // fix taken at utc a=nmea2lat(msg); // lat b=nmea2lat(msg); // lon c=nmea2i(msg); // fix quality d=nmea2i(msg); // number of satellites e=nmea2f(msg); // horizontal dilution of pos f=nmea2f16(msg); // altitude nmea_sep(msg,1),nmea_skip(0); // M g=nmea2f16(msg); // geoid msl diff nmea2i(msg); // dgps age in seconds nmea2i(msg); // dgps id if(!nmea_valid(msg,0)) return NEMA_INVALID; if(!c) return NEMA_INVALID gps[old]->lat=a; gps[old]->lon=b; gps[old]->mod=c; gps[old]->sat=d; gps[old]->hdp=e; gps[old]->alt=f; gps[old]->msl=g; return NEMA_UPDATE; } // ... else return NEMA_IGNORE; }
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.