c 使用 fgets 读取文件,strtok 导致分段错误

c reading a file using fgets, strtok causes segmentation fault

尝试逐行读取文件, 文件中的一行如下所示:

InputVector:0(1,3,4,2,40)

代码:

FILE *file = fopen(filename, "r");
char buff[26];
char *token;

while(fgets(buff, 26, (FILE*)file) != NULL) {

    buff[strlen(buff)] = '[=11=]';

    printf("%s\n", buff);
    token = strtok(buff, INV_DELIM1);
    printf("%s\n", token);
    token = strtok(NULL, INV_DELIM2);
    printf("%s\n", token);

    while(token != NULL) {
        token = strtok(NULL, INV_DELIM3);
        printf("%s\n", token);
    }
}

我的猜测是在 while 循环中 strtok() 没有在最后一个数字之后 return NULL 继续前进并导致分段错误。我尝试在 fgets() 之后将 "[=16=]" 添加到 buff 的末尾,但它没有做任何事情。

delim1: ":",
delim2: "(",
delim3: ",)"

我得到的输出是

InputVector:0(1,3,4,2,40)
InputVector
0
1
3
4
2
40
segfault

您重复出现如下代码:

token = strtok(NULL, INV_DELIM2);
printf("%s\n", token);

如果 strtok() returns 为 NULL,则它会传递给 printf(),由于 %s,它期望获得一个指向有效的以 0 结尾的字符串的指针在格式参数中。 NULL 不是指向有效的以 0 结尾的字符串的指针,因此发生了不好的事情,在您的情况下表现为崩溃。

解决方法:在尝试使用之前确保strtok()返回的指针不为NULL。

对未来的建议:学习如何使用 调试器 单步执行代码,并习惯使用 valgrind 来帮助跟踪内存问题。当您可以使用工具找出问题所在并准确了解问题所在时,您不必对正在发生的事情做出错误的猜测。

不要吝啬缓冲区大小。如果你最长的行可以是 25 个字符,那么不要使用 26 个字符作为缓冲区大小,而是:

#define MAXC 1024  /* constant for max characters in buf */
...
    char buff[MAXC] = 1024;

(由您决定,128 与任何其他值一样工作,保证输入长度的任何变化都不会超出数组的范围。我宁愿缓冲区是1000 个字符太长而不是 1 个字符太短。)

然后通过检查长度来验证每个 fgets 调用,并且 buff 中的最后一个字符是 '\n' 字符,例如

    while(fgets(buff, MAXC, file) != NULL) {
        size_t len = strlen (buff);
        if (len == MAXC - 1 && buff[len - 1] != '\n') {
            fputs ("error: line too long.\n", stderr);
            /* handle error - generally by reading and dicarding
             * characters until '\n' or EOF encounterd and 
             * then either calling continue or break
             */
        }

这将确保您在调用 strtok 之前拥有有效的字符串。

您不需要多个分隔符

然后,如评论中所述,不需要单独的分隔符。用 #define DELIM ":(,)\n" 定义或用 const char *delim = ":(,)\n" 声明的单个 delim 就足够了。然后你可以简单地循环遍历所有标记:

    for (token = strtok(buff, delim); token; token = strtok(NULL, delim))
        printf ("%s\n", token);

简短示例

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

#define MAXC 1024

int main (int argc, char **argv) {

    char buff[MAXC] = "";
    char *token = NULL;
    const char *delim = ":(,)\n";
    FILE *file = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!file) {    /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    while(fgets(buff, MAXC, file) != NULL) {
        size_t len = strlen (buff);
        if (len == MAXC - 1 && buff[len - 1] != '\n') {
            fputs ("error: line too long.\n", stderr);
            /* handle error - generally by reading and dicarding
             * characters until '\n' or EOF encounterd and 
             * then either calling continue or break
             */
        }

        for (token = strtok(buff, delim); token; token = strtok(NULL, delim))
            printf ("%s\n", token);
    }
    if (file != stdin) fclose (file);   /* close file if not stdin */

    return 0;
}

(如果你需要不同的结果,你可以调整delim

例子Use/Output

$ echo "InputVector:0(1,3,4,2,40)" | ./bin/strtok_delims
InputVector
0
1
3
4
2
40

检查一下,如果您还有其他问题,请告诉我。