vazamentos strtok e memória
Pergunta
Eu escrevi um analisador url simples usando strtok (). aqui está o código
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char *protocol;
char *host;
int port;
char *path;
} aUrl;
void parse_url(char *url, aUrl *ret) {
printf("Parsing %s\n", url);
char *tmp = (char *)_strdup(url);
//char *protocol, *host, *port, *path;
int len = 0;
// protocol agora eh por exemplo http: ou https:
ret->protocol = (char *) strtok(tmp, "/");
len = strlen(ret->protocol) + 2;
ret->host = (char *) strtok(NULL, "/");
len += strlen(ret->host);
//printf("char at %d => %c", len, url[len]);
ret->path = (char *)_strdup(&url[len]);
ret->path = (char *) strtok(ret->path, "#");
ret->protocol = (char *) strtok(ret->protocol, ":");
// host agora é por exemplo address.com:8080
//tmp = (char *)_strdup(host);
//strtok(tmp, ":");
ret->host = (char *) strtok(ret->host, ":");
tmp = (char *) strtok(NULL, ":");
if(tmp == NULL) {
if(strcmp(ret->protocol, "http") == 0) {
ret->port = 80;
} else if(strcmp(ret->protocol, "https") == 0) {
ret->port = 443;
}
} else {
ret->port = atoi(tmp);
}
//host = (char *) strtok(NULL, "/");
}
/*
*
*/
int main(int argc, char** argv) {
printf("hello moto\n");
aUrl myUrl;
parse_url("http://teste.com/Teste/asdf#coisa", &myUrl);
printf("protocol is %s\nhost is %s\nport is %d\npath is %s\n", myUrl.protocol, myUrl.host, myUrl.port, myUrl.path);
return (EXIT_SUCCESS);
}
Como você pode ver, eu uso strtok () muito, então eu posso "fatia" da URL. Eu não preciso de urls de apoio diferentes de HTTP ou HTTPS para que a forma como é feito resolve todos os meus problemas. A minha preocupação é (este está sendo executado em um dispositivo embutido) - Am I memória desperdício? Quando eu escrever algo como
ret->protocol = (char *) strtok(tmp, "/");
E depois chamar
ret->protocol = (char *) strtok(ret->protocol, ":");
Será que me primeiro RET ponteiro> protocolo realizada permanecem na memória? Eu pensei que talvez eu deveria definir a primeira chamada para um ponteiro tmp, chamada strtok RET> protocolo para a parte direita da string (a segunda chamada) e depois livre (tmp) apontando.
Qual deve ser a melhor maneira de usar strtok?
Solução
Para responder à sua pergunta diretamente, strtok só retorna um ponteiro para uma localização dentro da cadeia que dá-la como input-- não alocar nova memória para você, então não precisa ligar gratuitamente em qualquer um dos ponteiros que lhe dá em troca.
Por que vale a pena, você também pode olhar para "strchr" e "strstr", que são formas não destrutivos de procura de caracteres individuais ou sequências dentro cordas.
Também nota que a sua alocação de memória é problemático aqui-- você estiver usando strdup () para alocar uma nova seqüência dentro de sua função de análise, e então você está atribuindo fragmentos desse bloco de memória para campos de "ret". O seu interlocutor, assim, ser responsável por free'ing a corda strdup'd, mas desde que você está passando apenas a corda para trás implicitamente dentro ret, as necessidades do chamador saber magicamente o ponteiro passar para libertar. (Provavelmente RET> protocolo, mas talvez não, dependendo de como os olhares de entrada.)
Outras dicas
modifica strtok a corda no lugar, substituindo os caracteres especificados com NULL. Desde cordas em C são terminada em NULL, agora parece que o ponteiro original apontando para uma cadeia mais curta, embora a string original ainda está lá e ainda ocupa a mesma quantidade de memória (mas com personagens substituído com NULL). O fim da cadeia, eu acho, contém um double-NULL.
A resposta curta é: Mantenha um ponteiro para o início do seu buffer de string, e ter um outro ponteiro que é o ponteiro "corrente" na string como você analisá-lo. Quando você usar strtok ou iterar sobre a string de outras formas de atualizar o ponteiro "corrente", mas deixar o ponteiro início sozinho. Quando tiver terminado, free () o ponteiro começando. Sem memória vazada.
Você sabe que você pode continuar a análise do string usando NULL como o primeiro parâmetro de strtok?
Primeira chamada:
char* token = strtok(string, delimiters);
Depois:
token = strtok(NULL, other_delimiters);
Esta permitem simplificar o seu código:
int parse_url(char *url, aUrl *ret)
{
//get protocol
char* token = strtok(url, "/");
if( token == NULL )
return -1;
strcpy(ret->protocol, token);
strcat(ret->protocol, "//");
// skip next '/'
token = strtok(NULL, "/");
if( token == NULL )
return -1;
//get host
token = strtok(NULL, "/");
if( token == NULL )
return -1;
strcpy(ret->host, token);
// get path
token = strtok(NULL, "#");
if( token == NULL )
return -1;
strcpy(ret->path, token);
// ...
return 0;
}
Você pode ver que eu tinha um valor de retorno para saber se a análise foi feita com sucesso.
Obrigado por compartilhar o seu código! Corri-lo dentro valgrind e fixou dois vazamentos de memória gerados por funções strdup.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *protocol;
char *host;
int port;
char *path;
} URL;
void parse_url(char *url, URL *ret) {
char *tmp = (char *) strdup(url);
int len = 0;
ret->protocol = (char *) strtok(tmp, "/");
len = strlen(ret->protocol) + 2;
ret->host = (char *) strtok(NULL, "/");
len += strlen(ret->host);
ret->path = (char *) strdup(&url[len]);
ret->path = (char *) strtok(ret->path, "#");
ret->protocol = (char *) strtok(ret->protocol, ":");
ret->host = (char *) strtok(ret->host, ":");
tmp = (char *) strtok(NULL, ":");
if (tmp == NULL) {
if (strcmp(ret->protocol, "http") == 0) {
ret->port = 80;
} else if (strcmp(ret->protocol, "https") == 0) {
ret->port = 443;
}
} else {
ret->port = atoi(tmp);
}
}
void free_url(URL *url) {
free(url->path);
free(url->protocol);
}
int main(int argc, char** argv) {
URL url;
parse_url("http://example.com:3000/Teste/asdf#coisa", &url);
printf("protocol: %s\nhost: %s\nport: %d\npath: %s\n", url.protocol, url.host, url.port, url.path);
free_url(&url);
return (EXIT_SUCCESS);
}