【问题标题】:c reading a file using fgets, strtok causes segmentation faultc使用fgets读取文件,strtok导致分段错误
【发布时间】:2019-02-16 18:40:34
【问题描述】:

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

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)] = '\0';

    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() 在最后一个数字之后没有返回 NULL 并继续运行并导致分段错误。我尝试在fgets() 之后将"\0" 添加到buff 的末尾,但它没有做任何事情。

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

我得到的输出是

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

【问题讨论】:

  • 这不是已经导致了 SegFault buff[strlen(buff)] = '\0'; 吗?
  • 在尝试使用该指针之前,您永远不会检查strtok() 是否返回空指针。
  • 在while循环中,反转printf和strtok
  • buff[strlen(buff)] = '\0'; 没用。如果buff 已经是NUL-终止,它什么也不做。如果buff 尚未被NUL 终止,则它是未定义的行为。

标签: c io segmentation-fault fgets strtok


【解决方案1】:

你反复有这样的代码:

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    (由您决定,128 与任何其他值一样,可以保证输入长度的任何变化都不会超出数组的范围。我希望缓冲区也是 1000 个字符长于 1-char 太短了。)

    然后通过检查长度来验证每个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 之前拥有一个有效的字符串。

    您不需要多个分隔符

    然后,如 cmets 中所述,不需要单独的分隔符。使用#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)

    使用/输出示例

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

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

    【讨论】:

    • 我喜欢buffer[strcspn(buffer, “\n”)] = '\0';,因为它避免了代码中的条件,并且在字符串为空时起作用,等等。
    • @JonathanLeffler 是的,你说得很好。我猜是习惯。我一般使用strlen,所以我的正常检查是if (len &amp;&amp; buff[len - 1] == '\n') buff[--len] = '\0'; else if (len == MAXC - 1) { /* string too long */ }。 (在截断换行符后保留len 中更新的字符串长度)这里没有继续使用buff 作为需要删除换行符的完整字符串,所以我只是稍微缩短了测试以仅检查线适合buff。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-14
    • 1970-01-01
    • 1970-01-01
    • 2018-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多