我在 C 中出现段错误的原因是什么?

What is the cause of my segment fault in C?

当我编译我的代码时,我没有得到任何错误。但是,当我尝试 运行 它时,出现分段错误(核心已转储)。这是我的主要内容:

原码

void main(int argc, char *argv[]){
    if(argc < 3){
          return;
    }

    char *stop_list_name = argv[1];
    char *doc_names[argc - 2];

    int i;
    for(i = 0; i < argc; i++){
            doc_names[i] = argv[i];
    }

//create the array of stop words
    char *stopWords[50];
    char *word;
    int word_counter = 0;
    FILE *fp;
    fp = fopen(stop_list_name, "r");
    if(fp != NULL){
            while(!feof(fp)){
                    fscanf(fp, "%s", word);
                    stopWords[word_counter] = word;
                    word_counter++;
            }
    }

    fclose(fp);

    for(i = 0; stopWords[i] != '[=10=]'; i++){
            printf("%s", stopWords[i]);
    }
}

我很确定我的 while 循环有问题,但我不完全知道是什么问题,也不知道如何解决。

修改后的代码

看到答案后,我修改了我的代码,使其看起来像这样,但它仍然崩溃。现在怎么了?

int main(int argc, char *argv[]){
    if(argc < 3){
            return;
    }

    char *stop_list_name = argv[1];
    char *doc_names[argc - 2];

    int i;
    for(i = 2; i < argc; i++){
            doc_names[i-2] = argv[i];
    }

//create the array of stop words
    enum {MAX_STOP_WORDS = 50};
    char *stopWords[MAX_STOP_WORDS];
    int word_counter = 0;
    FILE *fp = fopen(stop_list_name, "r");
    if(fp != NULL){
            char word[64];
            int i;
            for(i = 0; i < MAX_STOP_WORDS && fscanf(fp, "%63s", word) == 1; i++){
                    stopWords[i] = strdup(word);
            }

            word_counter = i;
            fclose(fp);
    }

    for(i = 0; stopWords[i] != '[=11=]'; i++){
            printf("%s", stopWords[i]);
    }
}

原代码有问题

一个可能的问题来源是:

char *doc_names[argc - 2];

int i;
for(i = 0; i < argc; i++){
        doc_names[i] = argv[i];
}

您为 argc-2 个指针分配 space,然后继续将 argc 个指针复制到 space。那是缓冲区溢出(在这种情况下,也是堆栈溢出)。它很容易引起麻烦。一个合理的解决方法是:

for (i = 2; i < argv; i++)
    doc_names[i-2] = argv[i];

但是,您真的不需要复制参数列表;您可以只处理从索引 2 到最后的参数。我注意到显示的代码实际上并没有使用 doc_names,但越界赋值仍然会造成问题。


您没有分配 space 来读取一个词,也没有为每个停用词分配新的 space,也没有确保您不会溢出数组的边界'存储单词。

考虑使用:

enum { MAX_STOP_WORDS = 50 };
char *stopWords[MAX_STOP_WORDS];
int word_counter = 0;
FILE *fp = fopen(stop_list_name, "r");
if (fp != NULL)
{
    char word[64];
    for (i = 0; i < MAX_STOP_WORDS && fscanf(fp, "%63s", word) == 1; i++)
        stopWords[i] = strdup(word);
    word_counter = i;
    fclose(fp);
}

这个诊断出的问题绝对是您崩溃的一个合理原因。我在循环中使用了 i(在代码的前面声明),因为 word_counter 使循环控制线对于 SO 来说太长了。

严格来说,strdup() 不是标准 C 的一部分,但它是 POSIX 的一部分。如果没有POSIX,可以自己写:

#include <stdlib.h>
#include <string.h>

char *strdup(const char *str)
{
    size_t len = strlen(str) + 1;
    char *result = malloc(len);
    if (result != 0)
        memmove(result, str, len);
    return result;
}

您还展示了其他一些不良做法:

  • while (!feof(file)) is always wrong.
  • What should main() return in C and C++?
  • 如果 fopen() 有效,您应该只调用 fclose(fp),因此您需要将 fclose() 移动到 if 语句主体中。

修改代码中的问题

修改后的代码中有一个重要问题和几个非常小的问题:

  • 打印停用词的循环依赖于一个空指针(奇怪地拼写为 '[=32=]' — 这是一个有效但非常规的空指针拼写),但初始化代码没有'不设置空指针。

    有(至少)两个选项可以解决这个问题:

    1. 添加一个空指针:

         for (i = 0; i < MAX_STOP_WORDS-1 && fscanf(fp, "%63s", word) == 1; i++)
             stopWords[i] = strdup(word);
      
         stopWords[i] = 0;
         fclose(fp);
      }
      
      for (i = 0; stopWords[i] != '[=14=]'; i++)
          printf("%s\n", stopWords[i]);
      

      请注意,上限现在是 MAX_STOP_WORDS - 1

    2. 或者您可以使用 wordCount 代替条件:

      for (i = 0; i < wordCount; i++)
          printf("%s\n", stopWords[i]);
      

    我会选择第二个选项。

  • 这样做的一个原因是它避免了关于 wordCount 被设置和未被使用的警告——一个小问题。

  • 并且doc_names也设置了但没有使用。

我担心这些,因为我的默认编译器选项会为未使用的变量生成错误 — 因此代码在我修复之前不会编译。这导致:

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

int main(int argc, char *argv[])
{
    if (argc < 3)
    {
        fprintf(stderr, "Usage: %s stop-words docfile ...\n", argv[0]);
        return 1;
    }

    char *stop_list_name = argv[1];
    char *doc_names[argc - 2];

    int i;
    for (i = 2; i < argc; i++)
    {
        doc_names[i - 2] = argv[i];
    }
    int doc_count = argc - 2;

    // create the array of stop words
    enum { MAX_STOP_WORDS = 50 };
    char *stopWords[MAX_STOP_WORDS];
    int word_counter = 0;
    FILE *fp = fopen(stop_list_name, "r");
    if (fp != NULL)
    {
        char word[64];
        int i;
        for (i = 0; i < MAX_STOP_WORDS && fscanf(fp, "%63s", word) == 1; i++)
            stopWords[i] = strdup(word);

        word_counter = i;
        fclose(fp);
    }

    for (i = 0; i < word_counter; i++)
        printf("stop word %d: %s\n", i, stopWords[i]);

    for (i = 0; i < doc_count; i++)
        printf("document %d: %s\n", i, doc_names[i]);

    return 0;
}

并且,给定一个包含以下内容的停用词文件:

help
able
may
can
it
should
do
antonym
prozac

并编译它(源文件 sw19.c,程序 sw19):

$ gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition -Werror sw19.c -o sw19

和 运行 为:

$ ./sw19 stopwords /dev/null
stop word 0: help
stop word 1: able
stop word 2: may
stop word 3: can
stop word 4: it
stop word 5: should
stop word 6: do
stop word 7: antonym
stop word 8: prozac
document 0: /dev/null
$

您正在尝试将扫描的字符串存储到未初始化的指针,

fscanf(fp, "%s", word);

word,甚至没有初始化。

你可以为此使用静态缓冲区,就像这样

char word[100];

if (fscanf(fp, "%99s", word) != 1)
    word[0] = '[=11=]'; /* ensure that `word' is nul terminated on input error */

此外,while (!feof(fp)) 是错误的,因为 EOF 标记在 fscanf() 尝试读取文件末尾之前不会被设置,因此代码将额外迭代一次.在这种情况下,您将存储相同的 word 两次。

请注意,您还需要为指针数组分配 space,也许您可​​以在那里使用 malloc()