Hallo :)
Ich wollte mich nie damit beschäftigen, habe es jetzt mal versucht und
will es aber schon nicht mehr. Dennoch habe ich ein paar Fragen. Es geht
um strtok().
Wer benutzt strtok() wirklich?
Wo wird der geheime Hintergrundstring geseichert?
Wie bzw wann wird dieser wieder freigegeben?
Und kann man soetwas qualifizieren?
Meinen Versuchen zu urteilen, kann man strtok nicht verschachteln bzw
ihm neue delimiter geben, da dann die vorherigen Abhängigkeiten zerstört
werden. Macht ja auch Sinn. Oder geht das doch irgendwie?
Viele Grüße und noch einen schönen Abend euch :)
MaWin schrieb:> statisch
Das stimmt natürlich. Aber darauf ist es nicht schön Einfluss zu nehmen.
Der Speicher wird ggf. mit einem langen String über die komplette
Laufzeit zugemüllt. Da ja auch noch dynamisch allokiert wird.
strtok_r und es gibt auch strtok_s. Das ist schon bekannt. Konkret
meinte ich hier strtok.
Dominic schrieb:> MaWin schrieb:>> statisch>> Das stimmt natürlich. Aber darauf ist es nicht schön Einfluss zu nehmen.> Der Speicher wird ggf. mit einem langen String über die komplette> Laufzeit zugemüllt. Da ja auch noch dynamisch allokiert wird.
Es wird nichts dynamisch allokiert. Der übergeben String wird
modifiziert: Die Trennzeichen werden durch terminierende Nullen ersetzt.
ich verstehe das Problem nicht? benutze das zum Parsen von NMEA-Messages
und das läuft und läuft und läuft
ptr = strtok(line, ",*");
if (ptr == NULL)
return MSG_FAIL;
strcpy_s(manufacturer, ptr);
for (i = 1;i < 5;i++)
{
ptr = strtok(NULL, ",*");
if (ptr == NULL)
return MSG_FAIL;
switch (i)
{
case 1: strcpy_s(msg_type, ptr);break;
case 2: strcpy_s(msg_id, ptr);break;
case 3: strcpy_s(msg_value, ptr);break;
case 4: strcpy_s(msg_cs, ptr);break;
default: return MSG_FAIL;
}
}
Dominic schrieb:> Wer benutzt strtok() wirklich?
Wieder so einer der glaubt erst mal allen vor den Karren scheißen zu
müssen. Das wird seit 50 Jahren verwendet. Ob du es glaubst oder nicht,
es funktioniert.
> Wo wird der geheime Hintergrundstring geseichert?
Nirgendwo. Weil es keinen gibt. Es wird ein Pointer in den
Originalstring gespeichert und der Originalstring wird zerleg. Daher ist
das erste Argument von strtok() nicht const.
> Wie bzw wann wird dieser wieder freigegeben?
Wenn du ihn frei gibst - weil es dein Originalstring ist, nur inhaltlich
ein bisschen verändert.
> Und kann man soetwas qualifizieren?
Da hast du aber ein schönen Wort gelernt, qualifizieren. Nur was soll es
in diesem Zusammenhang bedeuten?
> Meinen Versuchen zu urteilen, kann man strtok nicht verschachteln bzw> ihm neue delimiter geben,
Delimiter kann man bei jedem Aufruf von strtok(NULL, delim) wechseln.
Verschachteln geht mit Schmerzen.
> da dann die vorherigen Abhängigkeiten zerstört> werden.
Welche Abhängigkeiten?
> Macht ja auch Sinn. Oder geht das doch irgendwie?
Ja, geht irgendwie, ist aber scheiße. Man berechnet sich den internen
State selber und speichert ihn extern. In etwa so:
1
char*s=...;// string to parse
2
char*last=s+strlen(s);// pointer to string's terminating \0
3
4
while(s<last){
5
char*token=strtok(s,delim);// next token
6
printf("%s\n",token);
7
s=token+strlen(token)+1;// start of next scan, 1 behind token end
Ich habe mich von den str* Routinen verabschiedet, benutze meist
std::string.
Bei den str* Routinen muss man immer aufpassen, ob da auch wirklich die
\0 dranhängt. z.B. strncpy() läßt das evtl weg.
Ist str* ist jetzt 40 Jahre alt. Damals war es ok, heute erwartet man
etwas mehr Komfort.
strtok() eignet sich sehr gut, wenn man nur wenig RAM hat, da man keine
Kopie braucht.
Heutzutage hat man aber massig RAM, daher benutze ich sscanf() zum
Zerlegen in einzelne Argumente. sscanf() kann auch die Anzahl der
Zeichen begrenzen, d.h. man kann zuverlässig einen Pufferüberlauf
verhindern.
Peter D. schrieb:> strtok() eignet sich sehr gut, wenn man nur wenig RAM hat, da man keine> Kopie braucht.
und auch, wenn man das Original nicht mehr braucht.
Ron-Hardy G. schrieb:> ich verstehe das Problem nicht? benutze das zum Parsen von NMEA-Messages> und das läuft und läuft und läuft
Niemand behauptet strtok würde nicht funktionieren.
Ich benutze es auch, sogar auf einem AVR zum primitiven Parsen von JSON.
Aber es ist halt ein Kind seiner Zeit.
Niemand der die komfortablen Alternativen aus C++, Java oder gar Python
mit seinen ausdruckstarken Funktionen kennt würde ohne Not wieder
Stringoperationen in C mit strtok und Konsorten machen.
Die Tatsache dass es einen internen Zustand gibt und der Ursprungsstring
manipuliert wird ist zwar technisch elegant und den Umständen
geschuldet, verwirrt Anfänger aber maximal.
Le X. schrieb:> Niemand der die komfortablen Alternativen aus C++, Java oder gar Python> mit seinen ausdruckstarken Funktionen kennt würde ohne Not wieder> Stringoperationen in C mit strtok und Konsorten machen.
Was sollen diese verallgemeinernden Sprüche? Alles hat seine
Berechtigung, auch heute noch. Selbst auf fetten Rechnern die TByte
lange Inputdaten verarbeiten müssen lohnt es sich darüber nachzudenken,
ob man Milliarden von dynamischen Speicheranforderungen/Freigaben macht
wenn man für jeden Dreck einen std::string benutzt oder native
Pointeroperationen bzw. die str* Funktionen benutzt. Da können ganz
schnell Faktor 10 dazwischen liegen. Für 100Byte lange Eingaben ist das
natürlich was komplett anderes.
temp schrieb:> Was sollen diese verallgemeinernden Sprüche?
Da ist nichts verallgemeinert.
Ich schrieb "ohne Not".
Nun wenn dein Scenario vorsieht dass du Terrabytes an Strings
bearbeitest, innerhalb von Mikrosekunden, ja, dann tut es wohl Not
strtok zu benutzen.
temp schrieb:> Alles hat seine> Berechtigung, auch heute noch
Ja klar hat es das auch heute noch. Hab doch geschrieben dass ich es
selber benutze.
Komm mal runter.
PittyJ schrieb:> Ich habe mich von den str* Routinen verabschiedet, benutze meist> std::string.
Das heißt, Du benutzt kein C. Dann würde ich auch keine
strIrgendwas-Funktionen verwenden.
temp schrieb:> Le X. schrieb:>>> Niemand der die komfortablen Alternativen aus C++, Java oder gar Python>> mit seinen ausdruckstarken Funktionen kennt würde ohne Not wieder>> Stringoperationen in C mit strtok und Konsorten machen.>> Was sollen diese verallgemeinernden Sprüche? Alles hat seine> Berechtigung, auch heute noch. Selbst auf fetten Rechnern die TByte> lange Inputdaten verarbeiten müssen lohnt es sich darüber nachzudenken,> ob man Milliarden von dynamischen Speicheranforderungen/Freigaben macht> wenn man für jeden Dreck einen std::string benutzt oder native> Pointeroperationen bzw. die str* Funktionen benutzt. Da können ganz> schnell Faktor 10 dazwischen liegen. Für 100Byte lange Eingaben ist das> natürlich was komplett anderes.
Und du meinst der g++ nimmt dann immer die schlechtest denkbare
Implementierung?
In der Regel bekommst du eine passende Implementierung welche eine gute
Performance hat, notfalls muss man eine Compiler Anweisung einbauen.
Bei 100TB sprechen wir über ganz andere Mechaniken, da würde ich schauen
direkt aus der NVMe SSD/NIC die Daten passend zu verarbeiten, ggf.
direkt ohne Umwege zur GPU/FPGA/ASIC leiten usw, aber hier bewegen wir
uns in einem ganz anderen Bereich der Optimierungen, hier bedarf es
genauer Kenntnisse der Zielsysteme. Aber das ist bei 99,9% aller
Anwendungen einfach nur überflüssig. Stell dir vor jemand bindet CUDA
für ein Hello World ein...
Und bei der Diskrepanz zwischen Aufgaben die Eingabemengen in dieser
Größe haben vs. in der Realität vielleicht 128Bit Eingaben haben ist
durchaus beindruckend wie manche meinen da optimieren zu müssen.
Hatte Mal einen der genau mit so einem String Kram rumoptimiert hat und
ewig diskutiert hat wie effizient sein Schrott sei. Der entsprechende
Snippet lief ein einziges Mal nicht einmal eine Millisekunde beim Start
bei einer Anwendung die Stunden läuft. Hab es durch den Profiler laufen
lassen, war nicht messbar. Was messbar war: dumme Meetings und schlechte
Wartbarkeit.
Dominic schrieb:> Ich wollte mich nie damit beschäftigen, habe es jetzt mal versucht und> will es aber schon nicht mehr. Dennoch habe ich ein paar Fragen. Es geht> um strtok().
Wozu die ganze Aufregung?
Wer sowas wie strtok benutzt, parst den String zweimal: einmal innerhalb
von strtok und dann nochmal selber - es sei denn daß man nach strtok das
Ergebnis wegschmeißt, ohne es anzuschauen.
Und du wolltest dich doch eigentlich garnicht damit befassen.
Irgendwie kommt mir das wieder mal wie eine Zombie-Diskussion vor. So
ähnlich wie die immer mal wieder hier auftauchenden Beiträge "Wieso...
...ist es damals so gekommen wie es gekommen ist?"
W.S.
Le X. schrieb:> Die Tatsache dass es einen internen Zustand gibt und der Ursprungsstring> manipuliert wird ist zwar technisch elegant und den Umständen> geschuldet, verwirrt Anfänger aber maximal.
Bis man die Doku gelesen hat. Das sollte man machen, bevor man die
Funktion benutzt.
Und technisch elegant ist strtok nur wirklich nicht.
Dominic schrieb:> Wer benutzt strtok() wirklich?> Wo wird der geheime Hintergrundstring geseichert?
Nachdem hier wieder ein ahnungsloser Psychopath seine Unkenntnis zum
Besten gibt:
> von MaWin (Gast)22.08.2022 22:52
in einer threadfesten Library natürlich threadlokal. Und es ist kein
String, sondern nur das eine Zeichen das im Originalstring gegen NUL
'\0' gepatcht wurde.
Und die Funktion funktioniert sehr gut, quasi wie beschrieben.
MaWin schrieb:> in einer threadfesten Library natürlich threadlokal. Und es ist kein> String, sondern nur das eine Zeichen das im Originalstring gegen NUL> '\0' gepatcht wurde.
Wie funktioniert das mit dem threadlokal?
1
"global":
2
chars="abc xyz";
3
4
thread1:
5
char*t=strtok(s," ");
6
7
thread2:
8
char*t=strtok(NULL," ");
ist soweit ich weiß nach Standard erlaubt und das Ergebnis klar
definiert, solange die zeitliche Reihenfolge geregelt ist.
Und mit "Zeichen" meinst du vermutlich Pointer auf Zeichen?
mh schrieb:> Wie funktioniert das mit dem threadlokal?
Auf den üblichen Plattformen gibt es einen Adressbereich, der in jedem
Thread auf eigenen Speicher verweist. Der könnte dafür genutzt werden.
https://en.wikipedia.org/wiki/Thread-local_storage
(prx) A. K. schrieb:> mh schrieb:>> Wie funktioniert das mit dem threadlokal?>> Auf den üblichen Plattformen gibt es einen Adressbereich, der in jedem> Thread auf eigenen Speicher verweist. Der könnte dafür genutzt werden.> https://en.wikipedia.org/wiki/Thread-local_storage
Und wie setzt man damit strtok korrekt um, sodass man strok aus mehreren
Threads auf den gleichen String anwenden kann (mein mini Beispiel)?
DerEgon schrieb:> PittyJ schrieb:>> Ich habe mich von den str* Routinen verabschiedet, benutze meist>> std::string.>> Das heißt, Du benutzt kein C. Dann würde ich auch keine> strIrgendwas-Funktionen verwenden.
Ja.
Das Unterforum heisst hier "PC-Programmierung". Geht also um was
größeres als einen AtMega.
Und ob ich im Makefile statt gcc nun g++ eintrage, ändert kaum etwas.
Nur kann ich damit dann auch ein paar praktische C++ Features benutzen.
mh schrieb:> Und wie setzt man damit strtok korrekt um, sodass man strok aus mehreren> Threads auf den gleichen String anwenden kann (mein mini Beispiel)?
strok selber ist explizit als "nicht thread safe" markiert.
Implementiert ist es wie folgt:
1
char*
2
strtok(char*s,constchar*delim)
3
{
4
staticchar*olds;
5
return__strtok_r(s,delim,&olds);
6
}
Wenn du unbedingt bei "strtok" bleiben willst, aber trotzdem
Thread-Sicher sein möchtest, könntest du dir ein
1
char*
2
strtok_ts(char*s,constchar*delim)
3
{
4
static__threadchar*olds;
5
return__strtok_r(s,delim,&olds);
6
}
Basteln (__thread bewirkt ThreadLocalStorage für das static olds, GCC
extension...)
Εrnst B. schrieb:> mh schrieb:>> Und wie setzt man damit strtok korrekt um, sodass man strok aus mehreren>> Threads auf den gleichen String anwenden kann (mein mini Beispiel)?> strok selber ist explizit als "nicht thread safe" markiert.
Auch danach habe ich nicht gefragt.
mh schrieb:> Auch danach habe ich nicht gefragt.
Na und? Aber darauf habe ich hingewiesen.
Für den gleichen String aus verschiedenen Threads, wenn gleichzeitig
noch andere Threads weitere Strings bearbeiten tut aber weder strtok
oder strtok_ts.
Dann musst du strtok_r verwenden, und dich selber um den olds-Pointer
kümmern.
Εrnst B. schrieb:> mh schrieb:>> Auch danach habe ich nicht gefragt.> Na und? Aber darauf habe ich hingewiesen.
Warum zitierst du mich, wenn du nicht auf mich antwortest?
Ich habe das Beispiel von oben vervollständigt, falls noch irgendjemand
erklären möchte, wie ein standardkonformes strtok mit thread_local
implementiert wird.
Wenn ich das richtig verstehe redet Ihr aneinander vorbei
strtok modifiziert den String selbst.
Wenn man mit mehreren Threads auf die Tokens dieses einen globalen
Strings zugreifen will, dann müsste man entweder im main das tokenizing
machen und den Threads das token mitgeben, oder man würde jedem Thread
eine Kopie des Strings mitgeben.
ein threadsafes strtok bedeutet nur dass strtok mit jeweils
unterschiedlichen (threadlokalen) Strings aufgerufen sich nicht
gegenseitig beeinflusst, indem die interne statische Variable
threadlocal ist.
Damit kannst du aber nicht in mehreren Threads auf den SELBEN globalen
String zugreifen.
Udo S. schrieb:> Wenn ich das richtig verstehe redet Ihr aneinander vorbei
Möglich, aber spätestens das vollständige Beispiel sollte klar sein.
> strtok modifiziert den String selbst.
Ja, wie schon mehrfach in diesem Thread angemerkt wurde und wie es in
der Doku steht.
> Wenn man mit mehreren Threads auf die Tokens dieses einen Strings> zugreifen will, dann müsste man entweder im main das tokenizing machen> und den Threads das token mitgeben, oder man würde jedem Thread eine> Kopie des Strings mitgeben.
Worauf basiert diese Aussage? Der Standard (C17) sagt lediglich, dass
strtok nicht sicher gegen Dataraces ist. In meinem Beispiel gibt es
soweit ich weiß kein Datarace.
> ein threadsafes strtok bedeutet nur dass strtok mit jeweils> unterschiedlichen (threadlokalen) Strings aufgerufen sich nicht> gegenseitig beeinflusst, indem die interne statische Variable> threadlocal ist.> Damit kannst du aber nicht in mehreren Threads auf den SELBEN globalen> String zugreifen.
Genau darum ging es mir. MaWin will strtok mit thread_local threadsafe
machen. Ich möchte wissen wie das gehen soll für ein standardkonformes
strtok.
mh schrieb:> In meinem Beispiel gibt es> soweit ich weiß kein Datarace.
Kannst du garantieren wann welche der beiden Threads ausgeführt werden?
Im Prinzip kann der erste Thread unterbrochen werden bevor das
strtok(string, " ");
ausgeführt wird und der zweite macht dann den
strtok(NULL, " ");
zuerst.
Udo S. schrieb:> mh schrieb:>> In meinem Beispiel gibt es>> soweit ich weiß kein Datarace.> Kannst du garantieren wann welche der beiden Threads ausgeführt werden?> Im Prinzip kann der erste Thread unterbrochen werden bevor das> strtok(string, " ");> ausgeführt wird und der zweite macht dann den> strtok(NULL, " ");> zuerst.
Die thrd_join stehen da nicht zum Spaß ...
mh schrieb:> MaWin will strtok mit thread_local threadsafe> machen.
Ja wenn eine Funktion threadsave ist kann man trotzdem mit der Unheil
anrichten wenn man sie in mehreren Threads auf die selben Daten
loslässt.
Threadsave heisst erst mal nur dass man die Funktion in mehreren Threads
mit jeweils eigenen Daten benutzen kann ohne dass da irgendwas sich
gegenseitig beeinflusst.
Das kann die originale strtok nicht leisten, weil sie eine interne
globale static Variable hat.
mh schrieb:> Die thrd_join stehen da nicht zum Spaß ...
Sorry übersehen, ich programmiere selten in C und da nicht
multithreaded.
Aber das funktioniert so mit einer threadsave strtok nicht, weil strtok
im 2. Thread die Info nicht mehr hat wo der aktuelle tokenzeiger ist.
Dein Beispiel würde nur mit dem nicht threadsaven strtok funktionieren.
Udo S. schrieb:> Aber das funktioniert so mit einer threadsave strtok nicht, weil strtok> im 2. Thread die Info nicht mehr hat wo der aktuelle tokenzeiger ist.> Dein Beispiel würde nur mit dem nicht threadsaven strtok funktionieren.
Dann ist es aber kein strtok mehr. Dann ist es eine threadsafe Funktion
die etwas ähnliches wie strtok macht. Genau deshalb habe ich ja
gefragt wie strtok mit thread_local gehen soll.
mh schrieb:> Genau deshalb habe ich ja> gefragt wie strtok mit thread_local gehen soll.
Aber das tut ja schon so, wie du in deinem Beispiel geschrieben hast.
Eben mit den Nebenbedingungen, dass du die Ausführungsreihenfolge der
Threads kontrollieren musst (du also eigentlich garkeine Threads
brauchst) und kein dritter Thread irgendwie gleichzeitig strtok
verwenden darf.
Eben weil "strtok" kein Thread Local Storage verwendet, die interne
static variable "olds" deswegen zwischen den Threads geteilt wird.
mit der "strtok_ts"-Variante geht das nicht mehr. Da kann jeder Thread
nach Belieben strtokken, ohne dass ihm andere Threads dazwischenfunken.
Aber: genau diese (eigentlich positive) Eigenschaft bewirkt, dass dein
absurdes, an den Haaren herbeigezogenes Beispiel nicht mehr laufen wird.
Εrnst B. schrieb:> Eben weil "strtok" kein Thread Local Storage verwendet, die interne> static variable "olds" deswegen zwischen den Threads geteilt wird.
Ich brauche keine Erklärung wie strtok oder seine _t, _ts oder _r
Verwandten funktionieren. Ich weiß auch, dass das Beispiel nicht sehr
realistisch ist. Es ist ein Beispiel. Dieses Beispiel sollte allerdings
laut Standard funktionieren (wenn nicht, warum?)
Ich möchte wissen, wie das von MaWin vorgeschlagene strtok mit
thread_local funktionieren soll.
Ich verstehe nicht so ganz was du willst.
In deinem Beispiel bekommt der 2. Thread beim Aufruf das 2. Token. Das
funktioniert nur deshalb weil du ihn synchronisiert hast und du dich
darauf verlässt, dass strtok eben nicht threadsave ist, du also die
Information vom 1. Thread im 2. Thread parat hast.
Ein threadsave strtok hat das natürlich nicht mehr. Dein Beispiel
verlässt sich ja gerade auf den im Normalfall (mehrere Threads benutzen
strtok mit unterschiedlichen Strings) falsche Verhalten des nicht
threadsave strtok.
mh schrieb:> Ich möchte wissen, wie das von MaWin vorgeschlagene strtok mit> thread_local funktionieren soll.
mit deinem Beispiel: Garnicht.
Was daran liegt, dass du dein Beispiel exakt danach ausgerichtet hast.
Dass die diversen MaWin-Clone keine allzu begnadeten C-Programmierer
sind, ist bekannt. Musst dir nicht extra Mühe geben, um die
bloßzustellen.
mh schrieb:> vielleicht meldet sich MaWin ja.
Ich empfehle einfach einen Blick in die Doku
https://docs.microsoft.com/de-de/cpp/c-runtime-library/reference/strtok-strtok-l-wcstok-wcstok-l-mbstok-mbstok-l?view=msvc-170
Hinweis
Jede Funktion verwendet eine statische Variable eines lokalen Threads,
um die Zeichenfolge in Token zu analysieren. Daher können mehrere
Threads diese Funktionen gleichzeitig aufrufen.
Wer will, kann natürlich auch in strtok.c im Library-Sourcecodeordner
gucken.
Die zu zerlegenden strings müssen natürlich auch threadlokal sein, z.B.
vom heap stammen.
MaWin schrieb:> mh schrieb:>> vielleicht meldet sich MaWin ja.> Ich empfehle einfach einen Blick in die Doku
Natürlich gibts keine Erklärung von dir ... Wie passt das mit dem
Standard zusammen?
MaWin schrieb:> Die zu zerlegenden strings müssen natürlich auch threadlokal sein, z.B.> vom heap stammen.
Aus welchem Hut hast du diese Anforderung gezogen? Und warum steht in
deinem Link nichts davon?
mh schrieb:> Aus welchem Hut hast du diese Anforderung gezogen?
Den, der meinen Kopf enthält. Ist bei dir nichts drin ?
> Und warum steht in deinem Link nichts davon?
Intelligenz ist durch nichts zu ersetzen
mh schrieb:> Ich möchte wissen, wie das von MaWin vorgeschlagene strtok mit> thread_local funktionieren soll.
Gar nicht, weil der Psychopath mal wieder dummes Zeug gelabert hat.
MaWin schrieb:> Ich empfehle einfach einen Blick in die Doku>> https://docs.microsoft.com/
Das ist eine MS-spezifische Erweiterung, mein lieber Psychomawin.
Ron-Hardy G. schrieb:> ich verstehe das Problem nicht? benutze das zum Parsen von NMEA-Messages> und das läuft und läuft und läuft
Ich konnte es damals nicht für NMEA verwenden und hab mir ein eigenes
my_strtok geschrieben, das einen Tick anders funktioniert. Bei NMEA sind
Felder, für die es gerade keinen Wert gibt, einfach leer. strtok liefert
aber blöderweise, wenn zwei Delimiter direkt nacheinander kommen, keinen
leeren String zurück, sondern überspringt den dann einfach. Für a,,b
liefert strtok dann nur zwei Token, nämlich a und b. Das leere in der
Mitte wird ignoriert.
mh schrieb:> Ich brauche keine Erklärung wie strtok oder seine _t, _ts oder _r> Verwandten funktionieren. Ich weiß auch, dass das Beispiel nicht sehr> realistisch ist. Es ist ein Beispiel. Dieses Beispiel sollte allerdings> laut Standard funktionieren (wenn nicht, warum?)
Wo steht denn im Standard, dass strtok kein thread-local storage
verwenden darf?
Rolf M. schrieb:> mh schrieb:>> Ich brauche keine Erklärung wie strtok oder seine _t, _ts oder _r>> Verwandten funktionieren. Ich weiß auch, dass das Beispiel nicht sehr>> realistisch ist. Es ist ein Beispiel. Dieses Beispiel sollte allerdings>> laut Standard funktionieren (wenn nicht, warum?)>> Wo steht denn im Standard, dass strtok kein thread-local storage> verwenden darf?
Ich habe (mehrfach) gefragt, wie das im Standard beschriebene Verhalten
implementiert werden kann, wenn thread_local verwendet wird. Ich habe zu
keinem Zeitpunkt geschrieben, dass thread_local verboten ist.
mh schrieb:> Ich habe zu keinem Zeitpunkt geschrieben, dass thread_local verboten ist.
Du hast es aber impliziert:
> Dieses Beispiel sollte allerdings laut Standard funktionieren (wenn> nicht, warum?)
Du sagst, dass der Standard verlangt, dass das funktioniert, also dass
kein thread-local storage genutzt wird, denn nur so kann es
funktionieren. Aber das verlangt der Standard gar nicht.
Ich glaube, hier ist irgendwie niemandem klar, welches Problem du
siehst.
Rolf M. schrieb:> Aber das verlangt der Standard gar nicht.
Genau. Der C-Standard kennt Threads überhaupt erst seit C11.
Hier https://en.cppreference.com/w/cpp/string/byte/strtok steht es auch
explizit, wie man annehmen soll, dass die Storage implementiert ist.
Was MS da macht ist halt eine Auslegung des Standards, die vor C11 auf
jeden Fall legitim war. Und auch heute noch eine vernünftige Sache ist.
MaWin schrieb:> Rolf M. schrieb:>> Aber das verlangt der Standard gar nicht.>> Genau. Der C-Standard kennt Threads überhaupt erst seit C11.> Hier https://en.cppreference.com/w/cpp/string/byte/strtok steht es auch> explizit, wie man annehmen soll, dass die Storage implementiert ist.
Für die Standardkonformität ist nur entscheidend, was im Standard steht,
und dort (bzw. dessen latest C17-draft) finde ich folgende Passagen:
"The functions in the standard library are not guaranteed to be
reentrant and may modify objects with static or thread storage
duration."
Und:
"The strtok function is not required to avoid data races with other
calls to the strtok function."
Ich verstehe das so, dass strtok thread-safe und ggf. auch mit
thread-local storage implementiert sein darf, aber nicht zwingend muss.
Rolf M. schrieb:> [...]
Ok, ich versuche es anders. Wie ist definiert, was strtok macht
(Parameter, Rückgabewert, Vor- Und Nachbedingungen)? Ich würde sagen,
dass das Verhalten durch den Text im Standard im Kapitel "7.24.5.7 The
strstr function" definiert wird. Und wenn ich diesen Text lese, sehe ich
nicht, warum mein Beispiel nicht funktionieren sollte.
Der Satz
1
The strtok function is not required to avoid data races with other calls to the strtok function.
der in eben diesem Kapitel steht, ist für das Beispiel nicht relevant,
da es keine data races geben kann.
Und der Satz
1
The functions in the standard library are not guaranteed to be
2
reentrant and may modify objects with static or thread storage
3
duration.
ändert nichts, da kein reentrant stattfindet. Und den Satz darf man auch
nicht lesen als "Every function in the standard library may modify
objects with static or thread storage duration"
mh schrieb:> Beispiel nicht relevant, da es keine data races geben kann.
Wieso eigentlich nicht? C garantiert dir mW nicht, dass die static
variable zwischen den Threads direkt synchronisiert wird. Da kann
beliebig etwas über die Caches Race conditions erzeugen. Besonders wenn
jemand eine besonders bösartige Architektur entwerfen sollte.
Den Sourcecode zu strtok_r habe ich z.B. hier gefunden.
https://code.woboq.org/userspace/glibc/string/strtok_r.c.html
Wem das Original nicht gefällt, der kann die 35-Zeilen lange Funktion
für sich auch einfach ändern. Fällt ja noch unter Fingerübung.
Interessant auch die 'normale' strtok Funktion.
https://code.woboq.org/userspace/glibc/string/strtok.c.html
Diese ruft intern einfach die _r Variante auf.
char *
strtok (char *s, const char *delim)
{
static char *olds;
return __strtok_r (s, delim, &olds);
}
avr schrieb:> Wieso eigentlich nicht? C garantiert dir mW nicht, dass die static> variable zwischen den Threads direkt synchronisiert wird.
thrd_join garantiert das.
avr schrieb:> mh schrieb:>> Beispiel nicht relevant, da es keine data races geben kann.> Wieso eigentlich nicht? C garantiert dir mW nicht, dass die static> variable zwischen den Threads direkt synchronisiert wird. Da kann> beliebig etwas über die Caches Race conditions erzeugen. Besonders wenn> jemand eine besonders bösartige Architektur entwerfen sollte.
Das wird durch den Standard geregelt. Wer hättes das gedacht?
Um das selbst nachvollziehen zu können, musst du das Kapitel
"Environment/Conceptual models/Execution environments/Multi-threaded
executions and data races lesen". Es ist drei Seiten lang, eine Seite
ist hier relevant. Der Rest beschäftigt sich mit atomics. Danach kannst
du die Beschreibung der drei vorkommenden Funktionen (strtok, thrd_join,
thrd_create) lesen. Wie einer der MaWins geschrieben hat, thrd_join ist
die Lösung.
mh schrieb:> Wie einer der MaWins geschrieben hat, thrd_join ist> die Lösung.
Nicht wirklich.
Bei der glibc teilen sich die Threads den strtok-state, bei der
Microsoft-Implementierung hat jeder Thread seinen eigenen State.
mit dem thrd_join kriegst du dein Beispiel so hingemogelt, dass es mit
der glibc in der aktuellen Version das tut, was du erwartest.
Mit einer anderen libc muss das nicht funktionieren, und auch mit einer
zukünftigen glibc-Version nicht...
"die Lösung" ist "strtok_r", da kannst du selber festlegen, welcher
Thread wann welchen State benutzt.
Εrnst B. schrieb:> mh schrieb:>> Wie einer der MaWins geschrieben hat, thrd_join ist>> die Lösung.>> Nicht wirklich.> Bei der glibc teilen sich die Threads den strtok-state, bei der> Microsoft-Implementierung hat jeder Thread seinen eigenen State.
Hast du zufällig die Frage gelesen, auf die ich mit meinem Beitrag
geantwortet habe? Weil deine Antwort ergibt in dem Zusammenhang keinen
Sinn.
> mit dem thrd_join kriegst du dein Beispiel so hingemogelt, dass es mit> der glibc in der aktuellen Version das tut, was du erwartest.
Was ist hier gemogelt? Was erwarte ich, das der Standard nicht
beschreibt?
> Mit einer anderen libc muss das nicht funktionieren, und auch mit einer> zukünftigen glibc-Version nicht...
Wenn sie standardkonform ist, muss es funktionieren. Alternativ kannst
du zeigen, wo mein Beispiel gegen den Standard verstößt.
mh schrieb:> Ok, ich versuche es anders. Wie ist definiert, was strtok macht> (Parameter, Rückgabewert, Vor- Und Nachbedingungen)? Ich würde sagen,> dass das Verhalten durch den Text im Standard im Kapitel "7.24.5.7 The> strstr function" definiert wird. Und wenn ich diesen Text lese, sehe ich> nicht, warum mein Beispiel nicht funktionieren sollte.
Kommt halt drauf an, wie man den Begriff "sequence" zu verstehen hat:
"A sequence of calls to the strtok function breaks the string pointed to
by s1 into a sequence of tokens". Der Standard scheint aber nicht zu
definieren, was er mit "sequence of calls" meint.
Ist eine "sequence of calls" etwas, das komplett in einem Thread läuft,
oder kann die auch auf mehrere Threads verteilt werden? Das einzige, was
grob in die Richtung geht, ist die Definition des Begriffs "sequenced
before", die sich explizit nur auf einen einzelnen Thread bezieht:
"Sequenced before is an asymmetric, transitive, pair-wise relation
between evaluations executed by a single thread, which induces a partial
order among those evaluations."
sequence -> nachfolgend. Wenn nicht gesagt wird "aber nur im selben
thread nachfolgend", dann finde ich ist das ziemlich klar, dass der
Thread dabei egal ist.
Wenn ich sage die Wand ist Rot, dann meine ich auch nicht, Wand ist Rot
während dem Sonnenuntergang. Man kann nicht einfach Zusatzanforderungen
anfügen. Das ändert die Bedeutung immer komplett.
DPA schrieb:> sequence -> nachfolgend.
sequence = zusammengehörende Reihe.
> Wenn ich sage die Wand ist Rot, dann meine ich auch nicht, Wand ist Rot> während dem Sonnenuntergang. Man kann nicht einfach Zusatzanforderungen> anfügen. Das ändert die Bedeutung immer komplett.
Also du denkst, dass "sequence" an der Stelle in komplett anderer
Bedeutung verwendet wird als in dem Begriff "sequenced before"? Denn da
steht ja, dass "sequenced before" bedeutet, dass alles in einem Thread
ist. Das liefe dann ungefähr darauf hinaus: "wenn ich sage, die Wand ist
rot, dann ist sie rot, aber wenn ich sage, die Tür ist rot, dann ist sie
in Wirklichkeit grün."
Rolf M. schrieb:> DPA schrieb:>> sequence -> nachfolgend.>> sequence = zusammengehörende Reihe.>>> Wenn ich sage die Wand ist Rot, dann meine ich auch nicht, Wand ist Rot>> während dem Sonnenuntergang. Man kann nicht einfach Zusatzanforderungen>> anfügen. Das ändert die Bedeutung immer komplett.>> Also du denkst, dass "sequence" an der Stelle in komplett anderer> Bedeutung verwendet wird als in dem Begriff "sequenced before"? Denn da> steht ja, dass "sequenced before" bedeutet, dass alles in einem Thread> ist. Das liefe dann ungefähr darauf hinaus: "wenn ich sage, die Wand ist> rot, dann ist sie rot, aber wenn ich sage, die Tür ist rot, dann ist sie> in Wirklichkeit grün."
Hat das "sequence of tokens" im gleichen Satz etwas mit "sequenced
before" zu tun? Wenn "sequenced before" gemeint ist, steht auch
"sequenced before" im Text. Oder warum sollte man davon ausgehen, dass
ausgerechnet bei strtok auf die korrekte Sprache verzichtet wurde?
Ich sehe da auch keinen Zusammenhang zwischen dem "sequenced before" und
dem "sequence of calls". Im "sequenced before" geht es um wenn A vor B
passiert, als ein shortcut, um klar zu sagen was da jeweils damit
gemeint ist, wenn effektiv "sequenced before" steht. Uhne das könnte man
das ganze Zeugs mit Sequenzpunkten vermutlich nur schwer festlegen.
Aber beim Satz mit "sequence of calls", geht es nur darum, dass die
Funktionsaufrufe zusammengehören, man eine "Folge von Funktionsaufrufen"
hat, die sich beeinflussen / zusammengehören. Ein komplett anderer
Kontext, und ein komplett anderer Zweck. Ich sehe nicht, wie das in
irgendeinsterweise etwas zusätzliches über diese Aufrufe aussagen
sollte.