我在 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=]'
— 这是一个有效但非常规的空指针拼写),但初始化代码没有'不设置空指针。
有(至少)两个选项可以解决这个问题:
添加一个空指针:
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
。
或者您可以使用 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()
。
当我编译我的代码时,我没有得到任何错误。但是,当我尝试 运行 它时,出现分段错误(核心已转储)。这是我的主要内容:
原码
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=]'
— 这是一个有效但非常规的空指针拼写),但初始化代码没有'不设置空指针。有(至少)两个选项可以解决这个问题:
添加一个空指针:
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
。或者您可以使用
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()
。