使用strtok和fgets时出现分段错误

时间:2019-09-29 04:44:48

标签: c fgets strtok

所以,我有一个程序,该程序从命令行获取初始参数,并尝试查看给定输入中它们出现了多少次。

例如在终端中,您将编写如下内容:

./main cat nap dog

然后它会说要查找3个单词,然后输入单词,并在其后输入一个句点:

Looking for words:
cat
cat
nap
.

然后程序将返回:

Result:
cat: 2
nap: 1
dog: 0

我想实现一种方法,以便您可以在一行上输入多个单词,并且仍然可以对它们进行计数。

我具有以下功能:

int process_stream(WordCountEntry entries[], int entry_count)
{
  short line_count = 0;
  char buffer[30];

  while (fgets(buffer, 30, stdin)) {
    buffer[strlen(buffer)-1]='\0';
    char* token = strtok(buffer, " \n");
    while (token != NULL){
      token = strtok(NULL, " \n"); 
      int i = 0;
      while (i < entry_count+1) {
        if (!strcmp(entries[i].word, token))
          entries[i].counter++;
        i++;
      }
      line_count++;
    }
  }
  return line_count;
}

我尝试使用“”的定界符标记每个给定的字符串,然后尝试移至下一行。但是,它只会分隔第一个单词,然后给出分段错误。

3 个答案:

答案 0 :(得分:2)

有很多错误的方法:

  1. i < entry_count+1:假设entry_count为1;从1 <2开始,您将针对未初始化且可能越界的entries[1]进行测试。我认为您想写< entry_count
  2. buffer[strlen(buffer)-1]=-如果strlen为0,则表示您正在写buffer[-1]-那里还有另一个段错误。我也不知道这条线的目的是什么。
  3. 您正在测试token是否不为空,然后再将其设置为strtok()。几乎可以肯定,第二次它将为null,您将针对null执行strcmp。那里还有另一个段错误。您需要将循环移动到循环的下一个strtok
  4. fgets始终在换行符处停止,无论如何都不需要strtok装置。

答案 1 :(得分:0)

最有可能的问题是将输入行拆分为令牌的循环。在输入数据上两次调用“ strtok”,只执行一次NULL检查。

如果第二个strtok调用返回NULL,则该代码将在strcmp上失败(崩溃)。考虑将循环体修改为:

  while (fgets(buffer, 30, stdin)) {
    buffer[strlen(buffer)-1]='\0';
    if ( strcmp(buffer, ".") == 0 ) break ;
    char* token = strtok(buffer, " \n");
    while (token != NULL){
      int i = 0;
      while (i < entry_count+1) {
        if (!strcmp(entries[i].word, token))
          entries[i].counter++;
        i++;
      }
      line_count++;
      // Place strtok here
      token = strtok(NULL, " \n");   }
    }

还请注意,发布的代码将解决导致每行上的第一个标记被忽略的问题。

答案 2 :(得分:0)

您的函数乱序处理令牌。在将指针token与以下各项一起使用之前,您将跳过第一个token,将其完全覆盖:

char* token = strtok(buffer, " \n");
while (token != NULL){
  token = strtok(NULL, " \n"); 

接下来,鉴于您使用" \n"的分隔符,以下各行是多余的。您尝试用以下代码覆盖的'\n'永远不会成为token的一部分:

buffer[strlen(buffer)-1]='\0';

您正在使用'.'来标记输入结束,因此您不需要处理最后一行。您可以通过使用简单的goto语句来中断嵌套循环,例如:

    while (fgets (buffer, MAXC, stdin)) {
        ...
        while (token != NULL) {
            if (!strcmp (token, "."))               /* compare != '.' */
                goto done;
            ...
    }
    done:;

    return line_count;
}

假设entry_countentries中元素的数量,那么您的+1会使您读取超出entries的范围,可能会导致SegFault。虽然无法验证是否给出了问题所缺少的完整代码,但看起来像您想要的那样:

        while (i < entry_count) {               /* +1 causeses UB */

您对token = strtok(NULL, " \n");的获取下一个标记的调用应该是循环中的最后一个语句,而不是第一个,否则您将跳过一个标记。

将其完全放在一起,看起来您需要类似以下内容:

#define MAXC 128    /* max number of characters per word/line */
...
int process_stream (WordCountEntry entries[], int entry_count)
{
    short line_count = 0;
    char buffer[MAXC];
    const char *delim = " \n";                      /* set delim once */

    while (fgets (buffer, MAXC, stdin)) {
        char *token = strtok(buffer, delim);
        while (token != NULL) {
            if (!strcmp (token, "."))               /* compare != '.' */
                goto done;
            int i = 0;
            while (i < entry_count) {               /* +1 causeses UB */
                if (!strcmp(entries[i].word, token))
                    entries[i].counter++;
                i++;
            }
            line_count++;
            token = strtok(NULL, delim);            /* now get next token */
        }
    }
    done:;

    return line_count;
}

简短示例

根据您的评论,希望每行能够处理单个单词或多个单词。上面的函数可以做到这一点,但是在您尚未发布的代码中,您可能还会遇到其他问题。为了验证上述功能,编写了一个简短的实现。只要您的代码提供相似的输入,上面的函数就可以满足您的需求。使用的简短示例是:

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

#define NENT  16    /* max number of entries (no. elements in array) */
#define MAXC 128    /* max number of characters per word / line */

typedef struct {
    char word[MAXC];
    int counter;
} WordCountEntry;

int process_stream (WordCountEntry entries[], int entry_count)
{
    short line_count = 0;
    char buffer[MAXC];
    const char *delim = " \n";                      /* set delim once */

    while (fgets (buffer, MAXC, stdin)) {
        char *token = strtok(buffer, delim);
        while (token != NULL) {
            if (!strcmp (token, "."))               /* compare != '.' */
                goto done;
            int i = 0;
            while (i < entry_count) {               /* +1 causeses UB */
                if (!strcmp(entries[i].word, token))
                    entries[i].counter++;
                i++;
            }
            line_count++;
            token = strtok(NULL, delim);            /* now get next token */
        }
    }
    done:;

    return line_count;
}

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

    WordCountEntry wce[NENT] = { { .word = "" } };
    int n = 0;

    if (argc < 2) {
        fputs ("error: insufficient arguments.\n", stderr);
        return 1;
    }

    for (int i = 1; i < (argc < NENT ? argc : NENT); i++) {
        for (int j = 0; j < i; j++) {
            if (!strcmp (wce[j].word, argv[i]))
                goto next;
        }
        strcpy (wce[i-1].word, argv[i]);
        n++;
        next:;
    }

    puts ("Looking for words:");

    if (!process_stream (wce, n))
        fputs ("(user canceled input)\n", stderr);

    puts ("\nResult:");
    for (int i = 0; i < n; i++)
        printf ("%s: %d\n", wce[i].word, wce[i].counter);
}

使用/输出示例

$ ./bin/wordcountentry cat nap dog
Looking for words:
cat
cat
nap
.

Result:
cat: 2
nap: 1
dog: 0

或每行包含多个单词:

$ ./bin/wordcountentry cat nap dog
Looking for words:
cat dog dog
cat dog
nap .

Result:
cat: 2
nap: 1
dog: 3

您的代码中可能还有其他问题,由于A Minimal, Complete, and Verifiable Example (MCVE)的缺乏,无法确定是否可以解决所有问题,但是考虑到您的情况,这应该可以解决可识别的问题可能还有其他。

如果您还有其他问题,请告诉我。