C - 使用 strtok 的分段错误

C - Segmentation fault using strtok

我有这段代码,它读取多个文件并打印某个值。读取文件后,在某个时刻我的 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.

  • 您是指“\0”吗?
if (line[len-1] == '\n') {
  line[len-1] = 0;
}

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

例如使用if(price2_token!=NULL) printf("%s\n", price2_token);

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

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

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

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

{"fiftyTwoWeekLow":32,"fiftyTwoWeekHigh":100, ... }[=11=]

注意字符串末尾的 [=18=]。另请注意,“fiftyTwoWeekLow”排在第一位,事实证明这非常重要。

现在让我们来跟踪这里的代码:

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

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

{"fiftyTwoWeekLow":32,"fiftyTwoWeekHigh":100[=13=] ... }[=13=]

请注意 strtok 已修改字符串:逗号已替换为 [=18=]。这样您就可以将 returned 指针 fiftyhigh_token 用作字符串,而不会看到逗号之后的所有内容。

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

接下来查找冒号,然后使用指向冒号的指针调用 strtok。由于您传递给 strok 的定界符是冒号,因此 strtok 会忽略冒号并且 return 是下一个标记,这(因为我们正在查看的字符串在“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);

这基本上是相同的过程,在你调用 strtokline2 之后,像这样:

{"fiftyTwoWeekLow":32[=16=]"fiftyTwoWeekHigh":100[=16=] ... }[=16=]

请注意,您只能找到“fiftyTwoWeekLow”,因为它在该行中位于“fiftyTwoWeekHigh”之前。如果它在之后出现,那么您将无法找到它,因为之前在“fiftyTwoWeekHigh”之后添加了 [=18=]。在那种情况下,strstr 将 returned NULL,这将导致 strtok 变为 return NULL,然后在将 NULL 传递给strstr.

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

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

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

您可能会将 ",{}" 作为定界符传递给 strtok,以便摆脱围绕该行的任何左花括号和右花括号。或者您可以在每个标记中查找它们,如果出现则忽略它们。

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

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