【问题标题】:strtok and memory leaksstrtok 和内存泄漏
【发布时间】:2010-12-02 11:53:05
【问题描述】:

我使用 strtok() 编写了一个简单的 url 解析器。这是代码

#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);
}

如您所见,我经常使用 strtok(),因此我可以“切片”网址。我不需要支持不同于 http 或 https 的 url,所以它的完成方式解决了我的所有问题。 我担心的是(这是在嵌入式设备上运行的) - 我在浪费内存吗? 当我写类似的东西时

ret->protocol = (char *) strtok(tmp, "/");

然后再调用

ret->protocol = (char *) strtok(ret->protocol, ":");

我的第一个指针 ret->protocol 是否保留在内存中?我想也许我应该将第一次调用设置为 tmp 指针,调用 strtok 将 ret->protocol 指向字符串的右侧部分(第二次调用),然后再调用 free(tmp)。

使用 strtok 的最佳方式应该是什么?

【问题讨论】:

  • 为了安全起见,您应该在每次 strtok() 之后检查 NULL 结果。万一你的'/'不存在,你不需要事情爆炸。一些 lint 类型的工具应该抱怨这种类型的事情。不要假设输入是正确的。

标签: c memory free malloc strtok


【解决方案1】:

为了直接回答您的问题,strtok 仅返回指向您作为输入提供的字符串内的位置的指针——它不会为您分配新内存,因此不需要在任何指针上调用 free它给你回报。

对于它的价值,您还可以查看“strchr”和“strstr”,它们是在字符串中搜索单个字符或序列的非破坏性方法。

还请注意,您的内存分配在这里存在问题——您正在使用 strdup() 在 parse 函数中分配一个新字符串,然后将该内存块的片段分配给“ret”字段。因此,您的调用者将负责释放 strdup 的字符串,但由于您只是在 ret 中隐式地将该字符串传递回,因此调用者需要神奇地知道要传递给 free 的指针。 (可能是 ret->protocol,但也可能不是,这取决于输入的外观。)

【讨论】:

  • +1 在 strchr (甚至是 strrchr,它反向执行)建议,可能通过循环迭代。这样,您最终不会弄乱您的原始字符串,但您仍然可以拉出您需要的部分。如果您以前从未使用过 strchr(),这里有一个方便的参考:cplusplus.com/reference/clibrary/cstring/strchr
【解决方案2】:

strtok 修改字符串,用 NULL 替换指定的字符。由于 C 中的字符串以 NULL 结尾,现在看来您的原始指针指向一个较短的字符串,即使原始字符串仍然存在并且仍然占用相同数量的内存(但字符替换为 NULL)。我认为字符串的结尾包含一个双空值。

简短的回答是:保留一个指向字符串缓冲区开头的指针,并在解析字符串时将另一个指针作为指向字符串的“当前”指针。当您使用 strtok 或以其他方式迭代字符串时,您会更新“当前”指针,但不理会开始指针。完成后,free() 开始指针。没有内存泄漏。

【讨论】:

    【解决方案3】:

    你知道你可以继续使用 NULL 作为 strtok 的第一个参数来解析字符串吗?

    第一次通话:

    char* token = strtok(string, delimiters);
    

    然后:

    token = strtok(NULL, other_delimiters);
    

    这使您可以简化代码:

    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;
    }
    

    你可以看到我有一个返回值来知道解析是否成功。

    【讨论】:

    • 这里请注意,如果您使用这个精简版本,您需要确保“ret”结构的 char * 字段具有为复制的字符串分配的内存。 (这里写的 strcat 会爆炸。)你也可以使用这个策略,但没有 strcat,只需直接分配给 ret->不管你最初做什么。
    【解决方案4】:

    感谢您分享您的代码!我在 valgrind 中运行它并修复了 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);
    }
    

    【讨论】:

      猜你喜欢
      • 2015-01-31
      • 1970-01-01
      • 2013-11-08
      • 2016-05-03
      • 2011-11-25
      • 2010-11-19
      • 2014-09-27
      • 2013-07-31
      • 1970-01-01
      相关资源
      最近更新 更多