A função strtok acompanha todos os compiladores ANSI C, e serve para dividir uma string em diversas cadeias menores. Um exemplo seria separar os campos de um registro no formato “campo a:campo b:campo c”, uma linha de dados tendo colunas divididas pelo caractere “:”.
Existem diversos motivos para evitar esta função a qualquer custo. Tive problemas recentes com o strtok(), e decidí compartilhar aí pelo menos 3 razões para evitar strtok(). São os chamados “efeitos colaterais” de funções “legadas” da biblioteca C.
Lá vão, 3 motivos para você evitar strtok():
1) A função não é reentrante. Você a chama a primeira vez utilizando a cadeia de caracteres que deseja dividir, e as chamadas seguintes passando um ponteiro nulo. Se alguma outra parte de seu programa utilizar a função strtok, a sequência inicial será comprometida.
2) strtok() alter a cadeia de caracteres que está sendo dividida – sem te avisar. Este motivo justifica não utilizar strtok() em hipótese alguma.
3) A string de origem, sendo dividida, não pode ser constante, deve ser um bloco de memória alocado e que comporte as alterações que a função efetua na cadeia original.
Os dois seguintes trechos ilustram na prática estes 3 defeitos. Aliás, talvez este seja um post inútil, visto que você não deve usar strtok(). No entanto é um exemplo de função padrão mal implementada, presente em 100% das bibliotecas C do mundo, o que provavelmente representa praticamente 100% de todos os computadores em funcionamento no mundo hoje*.
Este programa gera um Bus Error ou Segmentation Fault, dependendo de sua implementação:
/*
============================================================================
Name : strtok.c
Copyleft : ZeFonseca.com
Description : Demonstra efeitos colaterais de strtok()
============================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char *tok = NULL;
// temp é uma cadeia estatica, e o espaço alocado em memória tem
// apenas os caracteres necessários para
// armazenar todos os caracteres, mais um caractere nulo não visível na mesma
char *temp = "name:address:telephone:email";
// somamos um para acomodar o caractere nulo ao final da cadeia
int dll = strlen(temp) + 1;
tok = (char*)calloc(dll, 1);
if ( tok == NULL ) {
fprintf(stderr, "Unable to allocate %u bytes for token.\n", dll);
return EXIT_FAILURE;
}
// primeira chamada de strtok() gera Bus Error, o programa não passará daqui
tok = strtok(temp, ":");
// a linha acima ilustra os problemas descritos nos itens 2 e 3 acima
printf("%s\n", tok);
fflush(stdout);
while ( (tok = strtok(NULL, ":")) != NULL ) {
// strtok é chamada com um ponteiro nulo a partir da 2a chamada
// assim vemos que, caso qualquer outra função
// chame strtok() em outra thread
// a proxima iteracao deste loop retornara um valor sem sentido
// aqui ilustramos o ponto 1 dos problemas de strtok()
printf("%s\n", tok);
fflush(stdout);
}
return EXIT_SUCCESS;
}
Ao tentar manipular a string temp, strtok gera um Bus Error ou Segmentation Fault. O motivo da falha pode não ser óbvia de inicio: strtok() está tentando manipular a cadeia original, e não uma cópia dela! strtok() insere caracteres nulos nos locais onde encontra a divisão da cadeia procurada. A cadeia original não é uma área dinamicamente alocada, e sim uma área estática de memória. Quando strtok() tenta alterá-la, o sistema operacional encerra o processo.
A seguinte versão corrige este problema, alocando um espaço muito maior que o necessário e trabalhando com uma cópia da cadeia original. Como podemos perceber, este programa está longe de ser eficiente, terminamos com 3 cópias da mesma informação ao final do processo: a string original, uma cópia para trabalho, e as substrings contendo os trechos buscados, caso existam(somadas formam a cadeia original).
/*
============================================================================
Name : strtok2.c
Copyleft : ZeFonseca.com
Description : Desvia de efeitos colaterais de strtok() criando um frankenstein
============================================================================
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STR_BUFF_SIZE 1024
int main(void) {
// copiaremos a string para ca e dividiremos esta copia atraves de strtok()
char *dataline = NULL;
// guardaremos a versao original para comparar posteriormente
char *dataline2 = NULL;
char *tok = NULL; // armazenara cada token obtido
char *temp = "name:address:telephone:email"; // cadeia original para testes
// comprimento da string + 1 para acomodar o byte nulo \0
int dll = strlen(temp) + 1;
dataline = (char *)calloc(STR_BUFF_SIZE, 1);
memcpy(dataline, temp, dll);
dataline2 = (char *)malloc(STR_BUFF_SIZE);
tok = (char*)calloc(STR_BUFF_SIZE, 1);
if ( tok == NULL ) {
fprintf(stderr, "Unable to allocate %u bytes for token.\n", dll);
return EXIT_FAILURE;
}
memset(dataline2,0,dll);
memcpy(dataline2, dataline, dll);
printf("string 1 before: %s\n", dataline);
printf("string 2 before: %s\n", dataline2);
// strtok() nao causa Bus Error, pois dataline
// tem espaço suficiente para acomodar
// os efeitos colaterais de strtok()
tok = strtok(dataline, ":");
printf("%s\n", tok);
fflush(stdout);
while ( (tok = strtok(NULL, ":")) != NULL ) {
printf("%s\n", tok);
fflush(stdout);
}
// como strtok insere nulos na string,
// este printf mostrara apenas o primeiro campo
printf("string 1 after: %s\n", dataline);
// a string original sera totalmente impressa
printf("string 2 after: %s\n", dataline2);
return EXIT_SUCCESS;
}
Qual a alternativa? Diversos frameworks e bibliotecas fornecem alternativas seguras à strtok(). Procure evitar implementações da Microsoft(strtok_s) e outras variações como strtok_r. Em vez disso, use uma biblioteca bem implementada e bem testada como a glib.
* Praticamente todos os sistemas operacionais de ampla utilização são escritos em C e vão ligados à biblioteca padrão.