[c/c++] strtok weigert een string op tabs te splitsen

Pagina: 1
Acties:

  • Hertog
  • Registratie: Juni 2002
  • Laatst online: 21:31

Hertog

Aut bibat, aut abeat

Topicstarter
Goedenavond,

Eigenlijk zegt de topictitel al voldoende: ik probeer met strtok een string op tabs te splitsen, maar het lukt me niet. Het gaat om de volgende code (teruggebracht tot het meest triviale):

C++:
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string.h>

using namespace std;

void main()
{
    char* regel = "a\tb";
    char* a = strtok(regel, "\t");
    cout << a << endl;
}


Ik snap niet waarom dit niet werkt. Als ik op internet zoek naar strtok en tab kom ik vergelijkbare voorbeelden tegen, in de tutorial die uitlegd hoe strtok gebruikt moet worden. Iemand enig idee wat er aan de hand is? (Ik gebruik Visual C++ op een WindowsXP-computer.)


Overigens, een andere oplossing om een string op tabs te splitsen is ook welkom. Ik heb voor strtok gekozen omdat zowel de invoer (regel in het voorbeeld) als de uitvoer (a) uiteindelijk char* moeten zijn. En omdat gaat om een programma dat zo snel mogelijk moet werken leek het me niet handig (of misschien wel handig, maar niet efficient) eerst het hele zooitje naar c++-strings om te zetten en vervolgens weer terug.

"Pray, v. To ask that the laws of the universe be annulled in behalf of a single petitioner, confessedly unworthy." --Ambrose Bierce, The Devil's Dictionary


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 01:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

Hertog schreef op 13 maart 2004 @ 22:59:
Ik snap niet waarom dit niet werkt.
Het zou wel fijn zijn als je dan aan gaf waaruit jij de conclusie trekt dat het niet werkt :) Crasht je programma? Geeft ie een verkeerd resultaat terug? Zo ja, welk resultaat dan? Etc. Zie ook P&W FAQ - De "quickstart" :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Hertog
  • Registratie: Juni 2002
  • Laatst online: 21:31

Hertog

Aut bibat, aut abeat

Topicstarter
.oisyn schreef op 14 maart 2004 @ 00:16:
[...]


Het zou wel fijn zijn als je dan aan gaf waaruit jij de conclusie trekt dat het niet werkt :) Crasht je programma? Geeft ie een verkeerd resultaat terug? Zo ja, welk resultaat dan? Etc. Zie ook P&W FAQ - De "quickstart" :)
Hmm, dat detail ben ik inderdaad vergeten te vermelden ;)

Het programma crashed met VC++. Er komt een standaard windows 'wilt u een foutrapport versturen?' melding, maar ik denk niet dat microsoft mijn probleem gaat oplossen ;)

Inmiddels heb ik het ook al op een solaris en een linux pc geprobeerd, met respectievelijk een segmentation fault en een bus error als gevolg. Ik heb al geprobeerd de resultaatstring van te voren te alloceren (alhoewel dat niet nodig zou moeten zijn) maar dat hielp ook niet.

Het lijkt er op alsof strtok de tab 'mist' en vervolgens na het einde van de string verder probeert te lezen. Dat is tenminste de enige verklaring die ik kan vinden (Alhoewel je toch zou verwachten dat zo'n functie controleert of de string niet is afgelopen.)

"Pray, v. To ask that the laws of the universe be annulled in behalf of a single petitioner, confessedly unworthy." --Ambrose Bierce, The Devil's Dictionary


  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
http://www.mkssoftware.com/docs/man3/strtok.3.asp
The function strtok() writes null characters into the buffer pointed to by s1.
Er worden dus null characters geschreven naar die eerste parameter. En kijk eens wat je meegeeft: je geeft een string mee waarin niet geschreven mag worden. En ik denk dat als je je code compileert, dat je dan toch ook wel een flinke warning voor je neus krijgt...

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 01:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

Dat is idd de fout, maar waarom zou je een warning moeten krijgen? Door een nasty 'feature', ik meen overgeheveld uit C ivm compatibiliteit, kun je string literals aan non-const char *'s assignen.

De enige error dan wel warning die hij zou moeten krijgen is dat main een int moet retourneren, en dat void main () dus ongeldig is ;)

De oplossing is dus om je string in een beschrijfbaar stuk geheugen te zetten. Je kunt bijvoorbeeld gewoon een char array op de stack aanmaken dmv
C++:
1
2
3
4
int main ()
{
    char str[] = "hallo daar!";
}

[ Voor 28% gewijzigd door .oisyn op 14-03-2004 00:48 ]

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Hertog
  • Registratie: Juni 2002
  • Laatst online: 21:31

Hertog

Aut bibat, aut abeat

Topicstarter
Infinitive schreef op 14 maart 2004 @ 00:42:
http://www.mkssoftware.com/docs/man3/strtok.3.asp

[...]


Er worden dus null characters geschreven naar die eerste parameter. En kijk eens wat je meegeeft: je geeft een string mee waarin niet geschreven mag worden. En ik denk dat als je je code compileert, dat je dan toch ook wel een flinke warning voor je neus krijgt...
Volgens de pagina die ik gelezen had was het tweede argument (de string waarop gesplitst moet worden dus) een string waar niet in geschreven mag worden, const char*. Het eerste argument is gewoon char*. Daar mag toch gewoon in geschreven worden?
.oisyn schreef op 14 maart 2004 @ 00:46:

De enige error dan wel warning die hij zou moeten krijgen is dat main een int
moet retourneren, en dat void main () dus ongeldig is ;)
Windows doet daar niet zo moeilijk over - maar dit was dan ook alleen testcode.
De oplossing is dus om je string in een beschrijfbaar stuk geheugen te zetten. Je kunt bijvoorbeeld gewoon een char array op de stack aanmaken
Die code zou dan dit moeten worden:
C++:
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string.h>

using namespace std;

void main()
{
    char regel[] = "a\tb";
    char* a = strtok(regel, "\t");
    cout << a << endl;
}

Dat lost inderdaad het probleem op. Ik dacht overigens ergens gelezen te hebben dat het enige verschil tussen char * en char[] was dat een char * een \0 op het einde heeft. Weer wat geleerd. Waarom strtok als eerste argument 'char *' verwacht, snap ik dan weer niet. Zowiezo, waarom mag je bij een char * niet in het geheugen waar die pointer naar wijst schrijven? Zo'n beetje alle c-functies gebruiken 'char *' als type voor een string.

Overigens is mijn probleem hiermee nog niet helemaal opgelost. Ik had hier het probleem enigszins geabstraheerd, maar de echte code die ik nodig heb zit in een functie - die een char * als argument mee krijgt. Moet ik dan in die functie die string gaan kopieren? Daar gaat de efficientie... ;)

"Pray, v. To ask that the laws of the universe be annulled in behalf of a single petitioner, confessedly unworthy." --Ambrose Bierce, The Devil's Dictionary


  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Volgens de pagina die ik gelezen had was het tweede argument (de string waarop gesplitst moet worden dus) een string waar niet in geschreven mag worden, const char*. Het eerste argument is gewoon char*. Daar mag toch gewoon in geschreven worden?
Daar zit nu net de gemeenheid: als eerste parameter geef je wel iets mee dat het type char* heeft, maar op de regel erboven doe je:
C++:
1
char *regel = "a\tb";
Je kent hierbij een const char* toe aan een char*. Ik had verwacht dat dit op z'n minst een warning op zou leveren, maar zoals .oisyn al aangaf, wordt dit niet gedaan. Dus er wordt gewoon gezegd, dat die const char* gewoon een char* is, zonder er verder iets mee te doen. En dat levert je dus problemen op wanneer je ernaar gaat schrijven.

Ik zou me geen zorgen maken over het maken van zo'n kopietje en performance. Als dat namelijk echt een probleem zou vormen, dan kom je dat wel tegen als je je code gaat profilen. En dan kan je op dat moment wel actie ondernemen.

[ Voor 26% gewijzigd door Infinitive op 14-03-2004 01:24 ]

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 01:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik denk dat je nog maar even bij moet gaan lezen over pointers, arrays en wat const inhoudt ;)

Pointers en arrays in C/C++ zijn in feite hetzelfde: je hebt een referentie naar het eerste element in de lijst, en je hebt toegang tot de rest van de elementen aan de hand van de eerste. Het enige verschil tussen een T* en een T[] is dat je in het eerste geval echt een variabele hebt waar letterlijk een verwijzing staat naar een geheugenadres (waar het eerste element van de array zich bevindt). Bij T[] heb je niet echt zo'n variabele, je hebt de array zelf en de compiler weet waar deze zich bevindt. Een T[] kan automatisch naar een T* gecast worden door simpelweg het adres, dat al bekend is bij de compiler, in een variabele te stoppen.

Const wil echter zeggen dat het object waar de pointer naartoe wijst niet aan kunt passen; je kunt het alleen maar uitlezen. Een const char * wijst dus naar een string die je niet mag veranderen, en string literals, dus lappen tekst tussen quotes, zijn eigenlijk ook const char *'s. Echter zoals ik al zei, door een 'feature' is het mogelijk een string literal toe te kennen aan een non-const char *, en dan gaat het dus fout zodra je die probeert aan te passen (je krijgt dan geen compile error bij het aanpassen, een char * is immers geen const char *, dus je mag iets wijzigen)

Dus waar jij voor moet zorgen is dat je een string literal nooit toekent aan een char *, dan kan het in feite niet fout gaan. Maar als je een string literal ergens aan toe wilt kennen en als je het later aan wilt kunnen passen, dan moet je die string dus kopieren naar een stukje geheugen zodat het aangepast kan worden. Dat stukje geheugen kun je dynamisch alloceren dmv new, maar je kunt ook gewoon een lokale non-const array definieren. Gelukkig kun je string literals ook gebruiken om arrays mee te initialiseren, en dat is dus wat er gebeurt in mijn voorbeeldcode.

Als jouw strtok code in een functie staat die een char * als parameter heeft dan is er niets aan de hand, de code die de functie aanroept moet ervoor zorgen dat die array van chars ook daadwerkelijk schrijfbaar is (als dat niet had gehoeven dan had je de parameter ook beter kunnen definieren als const char *, maar dat is nu natuurlijk niet het geval)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • Korben
  • Registratie: Januari 2001
  • Laatst online: 14-11-2025

Korben

() => {};

.oisyn schreef op 14 maart 2004 @ 01:25:
Ik denk dat je nog maar even bij moet gaan lezen over pointers, arrays en wat const inhoudt ;)

Blablablablablablablabla. Bla.
Leuk, maar je legt niet uit waarom die string literal niet mag worden veranderd. Dat is namelijk omdat je zo...
C:
1
char *bla = "Hello, world";
... bla letterlijk naar het adres waar "Hello, world!" staat gedefinieerd laat wijzen. Geheugen ergens in de code van je applicatie, en dat geheugen is read-only. Dáárom krijg je een foutmelding. Als je dit had gedaan:
C:
1
2
char *bla = _strdup("Hello, world!");
// er vanuit gaande dat je MSVC gebruikt...
... was het wel goed gegaan.

.oisyn: Échte programmeurs haten PHP met een passie. Ben jij soms geen echte programmeur?


  • Hertog
  • Registratie: Juni 2002
  • Laatst online: 21:31

Hertog

Aut bibat, aut abeat

Topicstarter
.oisyn schreef op 14 maart 2004 @ 01:25:
Ik denk dat je nog maar even bij moet gaan lezen over pointers, arrays en wat const inhoudt ;)
Bedankt voor de heldere uitleg hierover.
Const wil echter zeggen dat het object waar de pointer naartoe wijst niet aan kunt passen; je kunt het alleen maar uitlezen. Een const char * wijst dus naar een string die je niet mag veranderen, en string literals, dus lappen tekst tussen quotes, zijn eigenlijk ook const char *'s.
Dat eerste had ik al begrepen, dat tweede wist ik nog niet...
Dus waar jij voor moet zorgen is dat je een string literal nooit toekent aan een char *, dan kan het in feite niet fout gaan. Maar als je een string literal ergens aan toe wilt kennen en als je het later aan wilt kunnen passen, dan moet je die string dus kopieren naar een stukje geheugen zodat het aangepast kan worden. Dat stukje geheugen kun je dynamisch alloceren dmv new, maar je kunt ook gewoon een lokale non-const array definieren. Gelukkig kun je string literals ook gebruiken om arrays mee te initialiseren, en dat is dus wat er gebeurt in mijn voorbeeldcode.
Nu snap ik tenminste ook waarom dit zo is. Ik ga eerst maar eens kijken of ik het zonder new ook voor elkaar gaat krijgen, deze code moet namelijk een paar 1000 keer worden uitgevoerd en volgens mij gaat het dan wel tellen als ik iedere keer new wil doen ;)

"Pray, v. To ask that the laws of the universe be annulled in behalf of a single petitioner, confessedly unworthy." --Ambrose Bierce, The Devil's Dictionary


  • .oisyn
  • Registratie: September 2000
  • Laatst online: 01:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

Korben schreef op 14 maart 2004 @ 04:46:
[...]
Leuk, maar je legt niet uit waarom die string literal niet mag worden veranderd.
implementatie-detail, je mag een const char * gewoon niet veranderen, of dit in het echt nu wel kan of niet doet er niet toe (en zo te zien begreep de TS mijn uitleg helemaal ;))
[code=c]
Als je dit had gedaan:
C:
1
2
char *bla = _strdup("Hello, world!");
// er vanuit gaande dat je MSVC gebruikt...
... was het wel goed gegaan.
Ten eerste is strdup geen ANSI, ten tweede zei ik al dat als je dat wilt doen dat je het dan op een stukje geheugen neer moet zetten waar je het wel mag veranderen. Dus of een dynamisch gealloceerde array (dat is in feit wat jij doet met een non-standard functie), of gewoon lokaal op de stack (wat een stuk efficienter is, hoewel je dat dan weer niet kan returnen vanuit een functie)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • igmar
  • Registratie: April 2000
  • Laatst online: 12-05 15:46

igmar

ISO20022

Wat OT misschien, maar een copy & paste uit de *NIX strtol() manual :

BUGS
Never use these functions. If you do, note that:

These functions modify their first argument.

The identity of the delimiting character is lost.

These functions cannot be used on constant strings.

The strtok() function uses a static buffer while
parsing, so it's not thread safe. Use strtok_r() if
this matters to you.

Als je de kennis hebt : Schrijf zelf een versie van split(), en laat strtok() voor wat het is.

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 01:00

.oisyn

Moderator Devschuur®

Demotivational Speaker

Misschien is het gebruik van std::string met z'n find () en substr () functies toch een stuk handiger :)

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


  • staefke
  • Registratie: December 2003
  • Laatst online: 19-05 22:28
als alternatief is er natuurlijk ook de boost::tokenizer library... :)

duh ?

Pagina: 1