【问题标题】:C - Segmentation fault using strtokC - 使用 strtok 的分段错误
【发布时间】:2023-03-08 08:35:01
【问题描述】:

我有这段代码,它读取多个文件并打印某个值。读取文件后,在某个时刻我的 while 循环停止并显示分段错误...

这是我的代码

int main () {

    const char s[2] = ",";
    const char s2[2] = ":";

    char var1[] = "fiftyTwoWeekHigh\"";
    char *fiftyhigh;
    char *fiftyhigh2;
    char *fiftyhigh_token;
    char *fiftyhigh2_token;
   
    char var2[] = "fiftyTwoWeekLow\"";
    char *fiftylow;
    char *fiftylow2;
    char *fiftylow_token;
    char *fiftylow2_token;

    char var3[] = "regularMarketPrice\"";
    char *price;
    char *price2;
    char *price_token;
    char *price2_token;
   
    FILE *fp;
    char* data = "./data/";
    char* json = ".json";
    char line[MAX_LINES];
    char line2[MAX_LINES];
    int len;
    char* fichier = "./data/indices.txt";

    fp = fopen(fichier, "r");

    if (fp == NULL){
        printf("Impossible d'ouvrir le fichier %s", fichier);
        return 1;
    }

    while (fgets(line, sizeof(line), fp) != NULL) {
        char fname[10000];
        len = strlen(line);
        if (line[len-1] == '\n') {
            line[len-1] = 0;
        }
        
        int ret = snprintf(fname, sizeof(fname), "%s%s%s", data, line, json);
        if (ret < 0) {
            abort();
        }
        printf("%s\n", fname);
        
        FILE* f = fopen(fname, "r");

        while ( fgets( line2, MAX_LINES, f ) != NULL ) {
            fiftyhigh = strstr(line2, var1);
            fiftyhigh_token = strtok(fiftyhigh, s);
            fiftyhigh2 = strstr(fiftyhigh_token, s2);
            fiftyhigh2_token = strtok(fiftyhigh2, s2);
            printf("%s\n", fiftyhigh2_token);

            fiftylow = strstr(line2, var2);
            fiftylow_token = strtok(fiftylow, s);
            fiftylow2 = strstr(fiftylow_token, s2);
            fiftylow2_token = strtok(fiftylow2, s2);
            printf("%s\n", fiftylow2_token);

            price = strstr(line2, var3);
            price_token = strtok(price, s);
            price2 = strstr(price_token, s2);
            price2_token = strtok(price2, s2);
            printf("%s\n", price2_token);
        
            //printf("\n%s\t%s\t%s\t%s\t%s", line, calculcx(fiftyhigh2_token, price2_token, fiftylow2_token), "DIV-1", price2_token, "test");
            
        }
        fclose(f);
    }
    fclose(fp);
    return 0;
}

输出是:

./data/k.json
13.59
5.31
8.7
./data/BCE.json
60.14
46.03
56.74
./data/BNS.json
80.16
46.38
78.73
./data/BLU.json
16.68
2.7
Segmentation fault

这就像我的程序停止,因为它无法到达某个文件的某个数据......有没有办法分配更多的内存?因为我的 MAX_LINES 已经设置为 6000。

【问题讨论】:

  • 输入是什么?确保返回的指针不是NULL,然后再将它们传递到下一步。
  • 输入是一个包含文件的目录。我的程序读取每个 .json 文件并打印这些文件的 3 个值(我必须在逗号和“:”之后取值)。
  • 如果你将一个返回值从fiftylow = strstr(line2, var2); 传递给strtok(),即NULL,那么你就是在欺骗strtok(),让它相信它是分裂过程的延续。
  • strtok 不会复制令牌。每个fgets 都会覆盖该行和您的令牌。使用mallocstrcpy
  • strdup() 如果您可以访问它,它会很方便,但重要的是不要设置任意数组限制,您可能会彻底崩溃。这段代码充满了猖獗的重复,这使得它比应有的更难理解。

标签: c file segmentation-fault fgets strtok


【解决方案1】:

我假设您文件中的行看起来像这样:

{"fiftyTwoWeekLow":32,"fiftyTwoWeekHigh":100, ... }

换句话说,它是某种 JSON 格式。我假设该行以“{”开头,所以每一行都是一个 JSON 对象。

您将该行读入line2,现在包含:

{"fiftyTwoWeekLow":32,"fiftyTwoWeekHigh":100, ... }\0

注意结尾处的\0 终止字符串。另请注意,“fiftyTwoWeekLow”排在第一位,这非常重要。

现在让我们来追溯这里的代码:

fiftyhigh = strstr(line2, var1);
fiftyhigh_token = strtok(fiftyhigh, s);

首先您调用strstr 来查找“fiftyTwoWeekHigh”的位置。这将返回一个指向该字段名称在该行中的位置的指针。然后您调用strtok 来查找将这个值与下一个值分开的逗号。我认为这是事情开始出错的地方。调用strtok 后,line2 看起来像这样:

{"fiftyTwoWeekLow":32,"fiftyTwoWeekHigh":100\0 ... }\0

请注意strtok 已修改字符串:逗号已替换为\0。这样您就可以将返回的指针 fiftyhigh_token 用作字符串,而不会看到逗号后面的所有内容。

fiftyhigh2 = strstr(fiftyhigh_token, s2);
fiftyhigh2_token = strtok(fiftyhigh2, s2);
printf("%s\n", fiftyhigh2_token);

接下来查找冒号,然后使用指向冒号的指针调用strtok。由于您传递给strok 的分隔符是冒号,strtok 会忽略冒号并返回下一个标记(因为我们正在查看的字符串在“100”之后结束,没有更多的冒号)是字符串的其余部分,也就是数字。

所以你得到了你的号码,但可能不是你预期的那样?第二次调用 strtok 确实没有意义,因为(假设 JSON 格式正确)“100”的位置只是 fiftyhigh2+1

现在我们尝试找到“fiftyTwoWeekLow:”

fiftylow = strstr(line2, var2);
fiftylow_token = strtok(fiftylow, s);
fiftylow2 = strstr(fiftylow_token, s2);
fiftylow2_token = strtok(fiftylow2, s2);
printf("%s\n", fiftylow2_token);

这个过程基本上是一样的,你调用strtok之后,line2是这样的:

{"fiftyTwoWeekLow":32\0"fiftyTwoWeekHigh":100\0 ... }\0

请注意,您只能找到“fiftyTwoWeekLow”,因为它位于该行中的“fiftyTwoWeekHigh”之前。如果它是在之后出现的,那么由于之前在“fiftyTwoWeekHigh”之后添加的\0,您将无法找到它。在这种情况下,strstr 将返回 NULL,这将导致 strtok 返回 NULL,然后您在将 NULL 传递给 strstr 后肯定会遇到 seg 错误。

所以代码对字段在行中出现的顺序非常敏感,它可能会失败,因为您的某些行的字段顺序不同。或者也许某些行中缺少某些字段,这会产生相同的效果。

如果您要解析 JSON,那么您应该真正使用专为此目的设计的库。但如果你真的想使用strtok,那么你应该:

  1. 阅读line2
  2. 调用strtok(line2, ",") 一次,然后在循环中重复调用strtok(NULL, ",") 直到它返回null。这会将行分解为每个看起来像 "someField":100 的标记。
  3. 从这些标记中的每一个中隔离字段名称和值(只需调用strchr(token, ':') 来查找值)。 不要在此处调用strtok,因为它会改变strtok的内部状态,您将无法使用strtok(NULL, ",")继续处理该行。
  4. 测试字段名称,并根据其值设置适当的变量。换句话说,如果是“fiftyTwoWeekLow”字段,则设置一个名为fiftyTwoWeekLow 的变量。您不必费心去除引号,只需将它们包含在您要比较的字符串中即可。
  5. 处理完所有令牌后(strtok 返回 NULL),对您设置的变量执行一些操作。

您可以将",{}" 作为分隔符传递给strtok,以消除围绕该行的任何打开和关闭的花括号。或者您可以在每个令牌中查找它们,如果它们出现则忽略它们。

您也可以将"\"{},:" 作为分隔符传递给strtok。这将导致strtok 发出交替的字段名称和值序列。您可以调用strtok 一次来获取字段名称,再次获取值,然后测试字段名称并对值进行处理。

使用strtok 是解析 JSON 的一种非常原始的方式,但只要您的 JSON 仅包含简单的字段名称和数字并且不包含任何本身包含分隔符的字符串,它就可以工作。

【讨论】:

    【解决方案2】:
    • 您的意思是“\0”吗?
    if (line[len-1] == '\n') {
      line[len-1] = 0;
    }
    

    我建议您使用gdb 查看段错误发生的位置以及原因。 我认为您不必分配更多内存。但是可能会发生段错误,因为您没有更多数据并且您仍然打印结果。

    if(price2_token!=NULL) printf("%s\n", price2_token); 为例。

    【讨论】:

    • 它的意思是写成换行符,虽然不是Removing trailing newline character from fgets() input的健壮方式请注意0'\0'是相同的:int的值为0,尽管后者是在char 数组的上下文中被认为更清晰。
    • 我试过了,还是没变……奇怪,当我的目录里有超过3个文件时,它会显示分段错误。
    猜你喜欢
    • 1970-01-01
    • 2018-02-07
    • 2014-03-10
    • 2012-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多