我正在读取我的C程序中的文件,并将其中的每个单词与我的单词进行比较,该单词通过命令行参数输入。但是我遇到了崩溃,我无法理解什么是错的。如何跟踪此类错误?我的情况有什么问题?
我的编译器是clang。代码编译得很好。运行时说“分段错误”。
这是代码。
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
char* temp = argv[1];
char* word = strcat(temp, "\n");
char* c = "abc";
FILE *input = fopen("/usr/share/dict/words", "r");
while (strcmp(word, c))
{
char* duh = fgets(c, 20, input);
printf("%s", duh);
}
if (!strcmp (word, c))
{
printf("FOUND IT!\n");
printf("%s\n%s", word, c);
}
fclose(input);
}
答案 0 :(得分:4)
这里的问题是你正试图用C语言处理字符串,就像你可能用另一种语言(如C ++或Java)一样,它们是可调整大小的向量,你可以轻松地附加或读取任意数量的数据。 / p>
C字符串的级别要低得多。它们只是一个字符数组(或指向这样一个数组的指针;数组可以被视为指向它们在C中的第一个元素的指针),并且该字符串被视为该数组中的所有字符,直到第一个空字符。这些数组是固定大小的;如果你想要一个任意大小的字符串,你需要使用malloc()
自己分配它,或者在你想要的大小的堆栈上分配它。
这里有一点令人困惑的是你使用的是非标准类型string
。鉴于上下文,我假设它来自您的cs50.h
,并且只是char *
的typedef。如果您实际使用char *
而不是string
,它可能会减少混淆;使用typedef会模糊真实情况。
让我们从第一个问题开始。
string word = strcat(argv[1], "\n");
strcat()
将第二个字符串附加到第一个字符串上;它从第一个字符串的null终止符开始,并将其替换为第二个字符串的第一个字符,依此类推,直到它在第二个字符串中达到null。为了使其工作,包含第一个字符串的缓冲区需要有足够的空间来容纳第二个字符串。如果没有,您可能会覆盖任意其他内存,这可能会导致程序崩溃或出现各种其他意外行为。
这是一个例子。假设argv[1]
包含单词hello
,缓冲区的空间就足够了。之后是其他一些数据;为了举个例子,我填写了other
,虽然它实际上并不是这样,但它可能是任何东西,它可能也可能不重要:
+---+---+---+---+---+---+---+---+---+---+---+---+
| h | e | l | l | o | \0| o | t | h | e | r | \0|
+---+---+---+---+---+---+---+---+---+---+---+---+
现在,如果您使用strcat()
追加"\n"
,您将获得:
+---+---+---+---+---+---+---+---+---+---+---+---+
| h | e | l | l | o | \n| \0| t | h | e | r | \0|
+---+---+---+---+---+---+---+---+---+---+---+---+
您可以看到我们已覆盖other
之后的hello
数据。这可能会导致各种问题。要解决此问题,您需要将argv[1]
复制到一个新的字符串中,该字符串有足够的空间加上一个字符(并且不要忘记尾随的空值)。您可以调用strlen()
来获取字符串的长度,然后为\n
添加1,为尾随空值添加一个,以获得所需的长度。
实际上,我建议您不要尝试在命令行中输入\n
这个词,而是建议从输入词中删除\n
,或者使用strncmp()
比较除最后一个字符之外的所有字符(\n
)。一般来说,最好在C中避免追加字符串,因为附加字符串意味着你需要分配内存并复制内容,这样做很容易犯错误,而且效率低下。更高级别的语言通常会为您处理细节,使附加字符串更容易,但仍然效率低下。
编辑后,您将其更改为:
char* temp = argv[1];
char* word = strcat(temp, "\n");
然而,这也存在同样的问题。 char *
是指向字符数组的指针。你的temp
变量只是复制指针而不是实际值;它仍然指向同一个缓冲区。这是一个例子;我正在为演示目的编造地址,在真机中这些东西之间会有更多的对象,但这足以达到演示的目的。
+------------+---------+-------+
| name | address | value |
+------------+---------+-------+
| argv | 1000 | 1004 |-------+
| argv[0] | 1004 | 1008 | --+ <-+
| argv[1] | 1006 | 1016 | --|---+
| argv[0][0] | 1008 | 'm' | <-+ |
| argv[0][1] | 1009 | 'y' | |
| argv[0][2] | 1010 | 'p' | |
| argv[0][3] | 1011 | 'r' | |
| argv[0][4] | 1012 | 'o' | |
| argv[0][5] | 1013 | 'g' | |
| argv[0][6] | 1014 | 0 | |
| argv[1][0] | 1016 | 'w' | <-+ <-+
| argv[1][1] | 1017 | 'o' | |
| argv[1][2] | 1018 | 'r' | |
| argv[1][3] | 1019 | 'd' | |
| argv[1][4] | 1020 | 0 | |
+------------+---------+-------+ |
现在,当您创建temp
变量时,您所做的只是将argv[1]
复制到新的char *
中:
+------------+---------+-------+ |
| name | address | value | |
+------------+---------+-------+ |
| temp | 1024 | 1016 | --+
+------------+---------+-------+
作为旁注,您也不应该在不检查argv[1]
是否大于1的情况下尝试访问argc
。如果某人未传递任何参数,则{{1} }本身无法访问。
我将继续讨论下一个问题。
argv[1]
这里,您指的是静态字符串 string c = "abc";
// ...
char* duh = fgets(c, 20, input);
。字符串中出现的字符串(如"abc"
)会进入程序内存的特殊只读部分。记住我说的话; "abc"
这里只是说string
的一种方式。所以char *
实际上只是指向这个只读内存部分的指针;它只有足够的空间来存储你在文本中提供的字符(4,c
和空字符终止字符串)。 abc
将第一个参数作为存储正在读取的字符串的位置,第二个参数是它所具有的空间量。所以你试图读取最多20个字节,进入一个只有4个空间的只读缓冲区。
您需要为堆栈中的读取分配空间,例如:
fgets()
或动态地,使用char c[20];
:
malloc()
答案 1 :(得分:1)
我看到的第一个问题是:
string word = strcat(argv[1], "\n");
您在此处将字符添加到缓冲区的末尾。 运行时环境为您分配的缓冲区,您应该将其视为只读。
修改强>
我担心您对代码的更改仍会产生相同的效果。
char* temp = argv[1];
temp
指向与argv[1]
相同的缓冲区。
您需要分配一个适当大小的缓冲区,并使用它。
char* temp = (char*)malloc(sizeof(char) * (strlen(argv[1]) + 2));
+2
用于在结尾处添加\n
和\0
。
比你这样做:
strcpy(temp, argv[1]);
strcat(temp,"\n");
答案 2 :(得分:1)
代码存在缺陷。另一个:
char* duh = fgets(c, 20, input);
在这里定义一个指向char的指针,不要初始化它(因此它包含一个随机值)然后你最多可以写20个字节到随机数据指向的地址。如果你很幸运,你只需要现金。如果没有,则覆盖其他一些重要数据。幸运的是,目前使用的大多数系统都不允许您访问另一个程序的地址空间,因此代码只会对自身造成严重破坏。
有问题的行可能如下:
#define BUFFERSIZE 1024
...
while (reasonable condition) {
char *duh = malloc(BUFERSIZE);
if (NULL == duh) { /* not enough memory - handle error, and exit */
}
duh = fgets(duh, BUFFERSIZE, input);
if (NULL == duh) { /* handle error or EOF condition */
} else { /* check that the line is read completely,
i.e. including end-of-line mark,
then do your stuff with the data */
}
free (duh);
}
当然,您只能分配缓冲区一次(在循环之外)并重用它。 #define
可以轻松调整最大缓冲区大小。
或者,在最近的系统上,您可以使用getline()
,它可以为您分配适当大小的缓冲区。 必须在循环结束时 free()
。
如果您使用的是Linux / BSD,请使用man
(例如man fgets
)获取有关这些功能的信息,否则请使用互联网或C上的书籍作为文档。
答案 3 :(得分:0)
首先,我的C知识是旧的,所以我不确定字符串是什么。无论哪种方式,它都是有用的,但并非绝对需要有一个很好的预归零缓冲区来读取文件的内容。因此,无论是零word
还是执行以下操作,都要先输入零。
#define IN_BUF_LEN 120
char in_buf[IN_BUF_LEN] = {0};
120个字符是一个安全的大小,假设大多数文本行的长度大约为80个字符。
其次,你的基础是strcmp
的值,而不是实际读取文件。它可能会完成同样的事情,但我会将while
基于文件末尾。
最后,您已将duh
声明为指针,而不是存储fgets
返回的地方。这也是一个问题。因此,duh
应与上述in_buf
类似地声明。
最后,您在编译时分配argv[1]
的值,而不是运行时。我看不出那是什么让你得到你想要的东西。如果您将temp
声明为指针,然后将argv[1]
指定给它,那么您将只有另一个指向argv[1]
的指针,但实际上并未将argv[1]
的值复制到argv[1]
一个局部变量。为什么不使用{{1}}?