strtok() se contente de mettre un \0 lorsqu'un des délimiteurs est rencontré. Pourquoi faire des déplacements de caractères (via ta fonction f_move() ) ?
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
Quand ton code arrive à la ligne 33, c'est qu'il n'y a plus de séparateur. Il faut bien retourner line. Mais lors de l'appel suivant, la fonction devra retourner immédiatement NULL, il reste à modifier le code pour se souvenir d'être arrivé ligne 33 (en modifiant une dernière fois line.)
Quand ton code arrive à la ligne 33, c'est qu'il n'y a plus de séparateur. Il faut bien retourner line. Mais lors de l'appel suivant, la fonction devra retourner immédiatement NULL, il reste à modifier le code pour se souvenir d'être arrivé ligne 33 (en modifiant une dernière fois line.)
Et ta ligne 22 devrait être supprimée.
Merci !!
c'est impec
char *f_strtok(char *src, char *sep)
{
if (!sep)
return (NULL);
static char *line;
if (src)
line = src;
if (!line)
return (NULL);
size_t i;
size_t j;
i = 0;
while (line[i])
{
j = 0;
while (sep[j])
{
if (sep[j] == line[i])
{
line[i] = 0;
if (!sep[j + 1])
{
char *tmp = line;
line = tmp + i + 1;
return (tmp);
}
}
j++;
}
i++;
}
char *tmp = line;
line = NULL;
return (tmp);
}
Mais du coup, j'ai une question: je viens de regarder le code correction et je me demande en quoi le code du zds est-il mieux que le mien ? Pouvez-vous détailler vos explications svp car pour moi, je préfère la mienne car je la trouve plus lisible.. Après est-il mieux de travailler avec le pointeur directement ou via un index ?
char *xstrtok(char *chaine, char *liste)
{
static char *dernier;
char *base = (chaine != NULL) ? chaine : dernier;
if (base == NULL)
return NULL;
separateur_au_debut:
for (char *sep = liste; *sep != '\0'; ++sep)
if (*base == *sep)
{
++base;
goto separateur_au_debut;
}
if (*base == '\0')
{
dernier = NULL;
return NULL;
}
for (char *s = base; *s != '\0'; ++s)
for (char *sep = liste; *sep != '\0'; ++sep)
if (*s == *sep)
{
*s = '\0';
dernier = s + 1;
return base;
}
dernier = NULL;
return base;
}
En comparant les deux codes, je dirais que ton code est globalement meilleur et plus clair. Sauf que le tien ne fonctionne pas au cause de ta ligne 24 qu'il faut supprimer (je ne vois pas ce que tu attends de ton test.)
Le code solution veut gérer le cas particulier d'un séparateur en début de chaine que tu n'as pas, mais je ne sais pas si c'est ce que fait la fonction strtok()(et pourquoi rien pour des séparateurs en fin ou des séparateurs accolés au milieu?), il faudrait lire la doc ou vérifier qui a juste.
Si la chaine est vide ou ne contient que des séparateurs, la solution retourne immédiatement NULL, alors que ton code retourne une chaine vide, puis NULL. Là aussi je pense que ton code est plus proche de strtok(), à confirmer.
Ton code se protège du cas où la liste des séparateurs est un pointeur NULL. Là encore voir ce que fait strtok(). Je penses que tu n'es pas conforme bien qu'un code sécuritaire soit plutôt positif. Mais ne jamais oublier qu'un programme doit toujours respecter ce qui est spécifié.
Ce qu'il faut prendre dans le code solution que tu n'as pas: - distinguer les 2 variables baseet dernierpermet de mieux comprendre; - utilise des boucles forplutôt que des whilequi est plus lisible; - définit les variables au plus près de leur utilisation (ta variable jest définie hors de la boucle sur i); - le caractère nul s'écrit plutôt '\0', la valeur 0 marche aussi mais c'est bien de rappeler que c'est un caractère; - inutile de mettre entre parenthèses l'expression passée à return.
Ce qui est meilleur dans ton code : - trop de returndans la solution, on s'y perd; - inutile de travailler sur des pointeurs, utiliser des indices est tout aussi optimum et plus lisible selon moi; - ne jamais jamais utiliser de gotoà moins de ne vraiment pas avoir le choix (dans les cas très très rares où ça améliore la lisibilité ou la maintenabilité.) - éviter d'avoir des variables mal nommées ou courtes sans raison (s ou listesont mal nommées dans la solution, tmpet lignechez toi mais moins gênant) le cas de i et jest une habitude acceptable.
Et *sep ou *ligne devraient avoir le type const char, pas le type char car en aucun cas on ne va modifier les délimiteurs. Mais je pense que le cours ne parle pas du tout de cette notion. En réalité la fonction strtok() est déclarée:
je rejoins Dalfab sur le point qu'il est nécessaire de désapprendre la norme 42 (while vs for, return parenthésé, …).
La solution de zds est tout sauf «classique».
Lorsqu'on écrit du code pour une bibliothèque alors on applique le principe DRY = Don't Repeat Yourself : on écrit des fonctions plutôt que de pisser du code. Par exemple, la libc contient une autre version de strtok qu'on dit réentrante et qui se nomme strtok_r. La fonction strtok doit donc s'écrire dans un tel contexte :
La lib fournit aussi deux autres fonctions utiles dans notre cas : strspn et strcspn :
NAME
strspn, strcspn - get length of a prefix substring
SYNOPSIS
#include <string.h>
size_t strspn(const char *s, const char *accept);
size_t strcspn(const char *s, const char *reject);
DESCRIPTION
The strspn() function calculates the length (in bytes) of the initial segment of s which consists entirely of bytes in
accept.
The strcspn() function calculates the length of the initial segment of s which consists entirely of bytes not in reject.
RETURN VALUE
The strspn() function returns the number of bytes in the initial segment of s which consist only of bytes from accept.
The strcspn() function returns the number of bytes in the initial segment of s which are not in the string reject.
du coup strtok_r peut s'écrire ainsi :
char *strtok_r(char *restrict str, const char *restrict delim, char **restrict saveptr)
{
if (str==NULL) str=*saveptr;
if (*str=='\0') {
*saveptr=str;
return NULL;
}
// on saute les delimiteurs au début
str += strspn(str, delim);
if (*str=='\0') {
*saveptr=str;
return NULL;
}
// on trouve la fin du token
char *end=str + strcspn(str,delim);
if (*end == '\0')
{
*saveptr = end;
return str;
}
*end='\0';
*saveptr=end+1;
return str;
}
Il nous reste deux fonctions de la libc à écrire : strspn et strcpsn. Elles sont très imilaires : la première compte le nombre de caractères au début d'une chaîne qui sont dans un ensemble, la seconde qui n'en sont pas … cela sent la spécialisation d'une fonction plus générale que l'on pourrait nommer strskip :
Profitons de l'occasion pour expliquer ces histoires de réentrance.
En gros, dans les débuts, C a été conçu pour UNIX, dans lequel les programmes sont lancés sous forme d'un processus qui peut lui même lancer d'autres processus (par fork).
Quand un processus (père) en crée un autre, le processus fils démarre avec un espace mémoire qui est une copie de celui du père, et à partir de là il vit sa vie.
Ce qui fait que les variables statiques qui s'y trouvent sont séparées. Le
staticchar*saveptr;
de l'un est indépendant du saveptr de l'autre. Chacun ses données.
--
Après ça on s'est mis à utiliser aussi des "processus légers" (threads) qui eux, travaillent dans le même espace mémoire que le processus père. Et donc partagent les variables statiques.
programme lance deux threads en parallèle. Le premier devrait afficher
1-> un
2-> deux
3-> trois
avec un petit délai pour chaque et le second quelque chose du même genre.
Sauf que quand on les lance, ça fait ça :
1-> un
2-> pif
1-> paf
Ce qui se passe : la variable statique qui sert à strtok est commune aux deux processus. Le second change sa valeur, ce qui fait que le premier processus travaille avec la chaîne du second. Halleluia.
Alors que si on avait appelé
task(& d1);
task(& d2);
ça marchait très bien.
Conclusion : la fonction strtok n'est pas capable de fonctionner correctement quand elle est appelée simultanément dans plusieurs processus d'un programme (thread safe). On dit qu'elle n'est pas réentrante.
Il y a d'autres trucs comme ça, il faut y faire attention. Pendant longtemps, c'était le cas de la variable globale errno, vous imaginez ?
PS: l'astuce géniale d'avoir une variable statique et deux façons d'appeler la même fonction, c'était pourri au départ.
Attention, la notion de multi-thread safety et de réentrance sont distinctes. Une fonction non réentrante comme strtok peut causer des soucis même avec un seul thread si elle est appelée lors d'une interruption logicielle par exemple. On peut construire des fonctions réentrantes mais non multi-thread safe, non réentrantes mais multi-thread safe, ou les deux à la fois ou ni une ni l'autre …
Attention, la notion de multi-thread safety et de réentrance sont distinctes. Une fonction non réentrante comme strtok peut causer des soucis même avec un seul thread si elle est appelée lors d'une interruption logicielle par exemple. On peut construire des fonctions réentrantes mais non multi-thread safe, non réentrantes mais multi-thread safe, ou les deux à la fois ou ni une ni l'autre …
C'est encore pire que ça. Si ma fonction f() appelle strtok(), alors elle casse le contexte de strtok() de toute fonction appelant f(). Les fonctions non réentrantes, pour moi c'est parmi les pires cochonneries qu'on puisse trouver dans un code.
- Edité par Marc Mongenet 13 septembre 2021 à 1:53:37
strtok zestedesavoir
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
On écrit "j'ai tort", pas "tord" qui est le verbe "tordre" à la 3ème personne de l'indicatif présent
En recherche d'emploi.
En recherche d'emploi.