man strtok

définition très personnelle du token
  • La première fois que l'on m'a parlé du token, je me suis bien demandé ce que c'était.
  • Une définition courte et lapidaire serait:
    • un token est un élément indivisible.
  • Je tente donc de préciser quelque peu:
    • un token est un élément atomique d'un surensemble.
    • par exemple: dans une phrase, on admet généralement que le mot est l'élément atomique (on ne descend pas au caractère).
    • la notion de token suppose celle de séparateur, pour différencier les token du surensemble.


strtok dans la bibliothèque standard
  • strtok : fonction de la bibliothèque string.h. Elle extrait des token d'une chaine.
    • strtok (char *chaine, char *separateurs)
  • une invocation singulière
    • le premier appel de strtok doit comporter un pointeur sur la chaine à découper en token.
    • les appels suivant à strtok doivent comporter un pointeur NULL en lieu et place de la chaine à découper.

char *chaine="ceci;est;une;chaine'';
char *token = strtok(chaine,";"); //premier appel
while (token = strtok(NULL,";"); //appels suivant



La devinette

  • Maintenant que vous savez tout sur les token, voici la devinette.
  • Ce source provoque un core-dump sous Linux.

#include <stdio.h>
#include <string.h>

char * get_token(char *string, char *sep)
{
  static char *token = 0;
  if (token) string=NULL;
  token = strtok (string, "sep");

  return token;
}


int main(void)
{
  char *token = 0;
  while (token=get_token("ceci;est;une;chaine",";")) {
    printf("token = %s
",token);
  }

  return 0;
}



strace est ton ami

chaine littérale et strtok
  • Tout réside dans l'appel de la fonction get_token.
    • Le paramètre passé à cette fonction est une chaine définie à la compilation (une chaine littérale).
    • Cette chaine est donc placée dans un segment read-only.
  • Sans même débugger, on peut le vérifier avec un strace qui indiquera la violation mémoire lors de l'appel à strtok :
  • gcc -Wall -W token_test.c && strace ./a.out
    • mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7dde000


résolution
  • Il ne faut donc jamais passer de chaines en dur dans votre code à strtok.
    • Il faut soit recopier la chaine dans la fonction d'arrivée, soit passer un pointeur sur un buffer contenant votre chaine.
  • Attention! Passer un pointeur sur une chaine définie à la compilation provoquera également un plantage.
    • char *chaine="ceci;est;une;chaine"; : core-dump
    • char chaine[]="ceci;est;une;chaine"; : ok , car ce n'est pas une chaine littérale, mais une liste d'initialisation de tableau ( (c) Fred. J. )
  • Encore une chose: strtok modifie la chaine source passée en paramètre. En voici la preuve:

#include <stdio.h>
#include <string.h>

int main(void)
{
  char chaine[]="ma;chaine";

  printf("origine: %s
",chaine);
  strtok(chaine,";");
  for (int i=0; i<sizeof(chaine); i++) 
    printf("[0x%X]%c ",chaine[i],chaine[i]);

  return 0;
}


  • Ce programme donne en sortie:

/tmp$ ./a.out 
origine: ma;chaine
[0x6D]m [0x61]a [0x0] [0x63]c [0x68]h [0x61]a [0x69]i [0x6E]n [0x65]e [0x0] 

  • Notez que le point-virgule a été remplacé par la valeur zéro, ce qui risque de donner des résultats assez comiques si vous réutilisez le buffer par la suite.



Et tok.

  • A noter qu'aux dernières nouvelles, MS-Windows ne passait pas ses chaines dans un segment en lecture seule.
    • Encore un bon exemple des pièges qui attendent les courageux adeptes du multiplateforme.
    • On peut certe remplacer courageux par les fous qui font tout en C.
  • La libc, c'est la bonne humeur au quotidien. Alors mangez-en, et parlez-en à vos amis.