由strdup和strtok引起的Segfault

时间:2016-10-24 15:52:46

标签: c strtok

我从大学教授那里得到了一份功课,我似乎发现了strtok的一些奇怪的行为

基本上,我们必须为我的类解析一个CSV文件,其中CSV中的标记数已知,最后一个元素可能有额外的","个字符。

一行的例子:

Hello,World,This,Is,A lot, of Text

令牌应输出为

1. Hello
2. World
3. This
4. Is
5. A lot, of Text

对于此作业,我们必须使用strtok。因此,我在其他一些SOF帖子中发现,使用带有空字符串的strtok(或传递"\n"作为第二个参数)会导致读取直到行尾。这对我的应用程序来说是完美的,因为额外的逗号总是出现在最后一个元素中。

我创建了这个有效的代码:

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

#define NUM_TOKENS 5

const char *line = "Hello,World,This,Is,Text";

char **split_line(const char *line, int num_tokens)
{
    char *copy = strdup(line);

    // Make an array the correct size to hold num_tokens
    char **tokens = (char**) malloc(sizeof(char*) * num_tokens);

    int i = 0;
    for (char *token = strtok(copy, ",\n"); i < NUM_TOKENS; token = strtok(NULL, i < NUM_TOKENS - 1 ? ",\n" : "\n"))
    {
        tokens[i++] = strdup(token);
    }

    free(copy);

    return tokens;
}

int main()
{
    char **tokens = split_line(line, NUM_TOKENS);

    for (int i = 0; i < NUM_TOKENS; i++)
    {
        printf("%s\n", tokens[i]);
        free(tokens[i]);
    }
}

现在这有效,应该让我充分信任,但我讨厌这个不应该需要的三元组:

token = strtok(NULL, i < NUM_TOKENS - 1 ? ",\n" : "\n");

我想用这个版本替换方法:

char **split_line(const char *line, int num_tokens)
{
    char *copy = strdup(line);

    // Make an array the correct size to hold num_tokens
    char **tokens = (char**) malloc(sizeof(char*) * num_tokens);

    int i = 0;
    for (char *token = strtok(copy, ",\n"); i < NUM_TOKENS - 1; token = strtok(NULL, ",\n"))
    {
        tokens[i++] = strdup(token);
    }

    tokens[i] = strdup(strtok(NULL, "\n"));

    free(copy);

    return tokens;
}

这让我感到非常好,因为更容易看出有最后的案例。你也摆脱了奇怪的三元运算符。

可悲的是,这段错误!我不能为我的生活找出原因。

编辑:添加一些输出示例:

[11:56:06] gravypod:test git:(master*) $ ./test_no_fault 
Hello
World
This
Is
Text
[11:56:10] gravypod:test git:(master*) $ ./test_seg_fault 
[1]    3718 segmentation fault (core dumped)  ./test_seg_fault
[11:56:14] gravypod:test git:(master*) $ 

3 个答案:

答案 0 :(得分:2)

在冒险将strtok传递给其他功能之前,请检查NULL的返回值。你的循环再次调用strtok一次。

通常使用此返回值来控制循环,然后您不受数据的限制。至于分隔符,最好保持简单,不要尝试任何花哨的东西。

char **split_line(const char *line, int num_tokens)
{
    char *copy = strdup(line);
    char **tokens = (char**) malloc(sizeof(char*) * num_tokens);
    int i = 0;
    char *token;
    char delim1[] = ",\r\n";
    char delim2[] = "\r\n";
    char *delim = delim1;                   // start with a comma in the delimiter set

    token = strtok(copy, delim);
    while(token != NULL) {                  // strtok result comtrols the loop
        tokens[i++] = strdup(token);
        if(i == NUM_TOKENS) {
            delim = delim2;                 // change the delimiters
        }
        token = strtok(NULL, delim);    
    }
    free(copy);
    return tokens;
}

请注意,您还应检查mallocstrdup的返回值并正确释放内存

答案 1 :(得分:0)

当你到达最后一个循环时,你将获得

for (char *token = strtok(copy, ",\n"); i < NUM_TOKENS - 1; token = strtok(NULL, ",\n"))
  1. loop body
  2. 循环增量步骤,即token = strtok(NULL, ",\n")(错误的第二个arg)
  3. 循环继续检查i < NUM_TOKENS - 1
  4. 即。它仍然会调用strtok,即使您现在已超出范围。您还可以在此处获得数组索引的详细信息:您希望初始化i=0而不是1。

    你可以通过例如来避免这种情况。

    • 使初始strtok成为循环之外的特殊情况,例如

      int i = 0;
      tokens[i++] = strdup(strtok(copy, ",\n"));
      
    • 然后移动循环中的strtok(NULL, ",\n")

    我也很惊讶你想要那里的\n,或者甚至需要打电话给最后一个(不知道那已经指向其余的字符串吗?如果你只是试图删除尾随换行符有更简单的方法)但我多年来都没有使用strtok。

    (顺便说一下,你也没有释放malloced数组,你存储了字符串指针。这就是说,因为那时程序的结束并不是那么重要。)

答案 2 :(得分:0)

请记住strtok在分隔符字符串(strtok()的第二个参数)中找到任何字符时标识一个标记 - 它不会尝试匹配整个分隔符字符串本身。

因此,首先从不需要三元运算符 - 字符串将根据输入字符串中, OR \n的出现进行标记化,因此以下工作原理:

for (token = strtok(copy, ",\n"); i < NUM_TOKENS; token = strtok(NULL, ",\n"))
{                                                                                                         
    tokens[i++] = strdup(token);                                                                          
} 

第二个例子是segfaults,因为它在退出for循环时已经将字符串末尾的输入标记为输出。再次调用strtok()会将token设置为NULL,并且会在strdup()指针上调用NULL时生成段错误。删除对strtok的额外调用会产生预期结果:

for (token = strtok(copy, ",\n"); i < NUM_TOKENS - 1; token = strtok(NULL, ",\n"))
{                                                                                 
    tokens[i++] = strdup(token);                                                  
}                                                                                 
tokens[i] = strdup(token);