0

我正在读取我的 C 程序中的文件并将其中的每个单词与我的单词进行比较,该单词是通过命令行参数输入的。但是我崩溃了,我不明白出了什么问题。如何跟踪此类错误?我的情况有什么问题?

我的编译器是铿锵的。代码编译得很好。运行时显示“分段错误”。

这是代码。

#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);    
}
4

4 回答 4

4

这里的问题是您试图在 C 中处理字符串,就像在另一种语言(如 C++ 或 Java)中一样,它们是可调整大小的向量,您可以轻松地将任意数量的数据附加或读取到其中。

C字符串的级别要低得多。它们只是一个字符数组(或指向此类数组的指针;无论如何,数组可以被视为指向它们在 C 中的第一个元素的指针),并且字符串被视为该数组中的所有字符,直到第一个空字符. 这些数组是固定大小的;如果你想要一个任意大小的字符串,你需要自己分配它malloc(),或者在堆栈上分配你想要的大小。

这里有点令人困惑的一件事是您使用的是非标准类型string。鉴于上下文,我假设它来自您的cs50.h, 并且只是一个 typedef 到char *. 如果您实际使用char *而不是,它可能会减少混淆string;使用 typedef 掩盖了真正发生的事情。

让我们从第一个问题开始。

    string word = strcat(argv[1], "\n");

strcat()将第二个字符串附加到第一个字符串上;它从第一个字符串的空终止符开始,并用第二个字符串的第一个字符替换它,依此类推,直到它在第二个字符串中达到空值。为了使其工作,包含第一个字符串的缓冲区需要有足够的空间来容纳第二个字符串。如果没有,您可能会覆盖任意其他内存,这可能会导致您的程序崩溃或出现各种其他意外行为。

这是一个插图。假设argv[1]包含单词hello,并且缓冲区具有与其所需的空间一样多的空间。之后是其他一些数据;我已经填写other了示例,虽然它实际上不是那样,它可以是任何东西,它可能重要也可能不重要:

+---+---+---+---+---+---+---+---+---+---+---+---+
| h | e | l | l | o | \0| o | t | h | e | r | \0|
+---+---+---+---+---+---+---+---+---+---+---+---+

现在如果你使用strcat()append "\n",你会得到:

+---+---+---+---+---+---+---+---+---+---+---+---+
| h | e | l | l | o | \n| \0| t | h | e | r | \0|
+---+---+---+---+---+---+---+---+---+---+---+---+

你可以看到我们已经覆盖了other之后的数据hello。这可能会导致各种问题。要解决此问题,您需要将您的字符串复制argv[1]到一个新字符串中,该字符串有足够的空间加上一个字符(并且不要忘记尾随的空值)。您可以调用strlen()以获取字符串的长度,然后为 . 添加 1,\n为结尾的 null 添加 1,以获得您需要的长度。

实际上,与其尝试将 a 添加\n到您从命令行输入的单词中,我建议\n您从输入的单词中删除 ,或者使用strncmp()来比较除最后一个字符(the \n)之外的所有字符。一般来说,最好在 C 中避免附加字符串,因为附加字符串意味着您需要分配内存并复制内容,这样做很容易出错,而且效率低下。高级语言通常会为您处理细节,使附加字符串变得更容易,尽管仍然同样低效。

编辑后,您将其更改为:

    char* temp = argv[1];
    char* word = strcat(temp, "\n");

然而,这也有同样的问题。Achar *是指向字符数组的指针。您的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]不检查argc大于 1 的情况下尝试访问。如果有人没有传入任何参数,那么argv[1]它本身就无法访问。

我会继续下一个问题。

    string c = "abc";

    // ...

        char* duh = fgets(c, 20, input);

在这里,您指的是静态字符串"abc"。在源代码中出现的字符串,如"abc",进入程序内存的一个特殊的只读部分。记住我说的话;string这里只是一种说法char *。所以c实际上只是指向这个只读内存部分的指针;并且它只有足够的空间来存储您在文本中提供的字符(4,用于abc和终止字符串的空字符)。fgets()它的第一个参数是一个存储正在读取的字符串的位置,第二个参数是它拥有的空间量。因此,您尝试将最多 20 个字节读取到一个只读缓冲区中,该缓冲区只有 4 个空间。

您需要在堆栈上分配用于读取的空间,例如:

char c[20];

或动态地,使用malloc()

char *c = malloc(20);
于 2012-11-18T22:11:10.760 回答
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)获取有关功能的信息,否则请使用 Internet 或一本像样的 C 书籍来获取文档。

于 2012-11-18T22:14:11.130 回答
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");
于 2012-11-18T21:51:11.827 回答
0

首先,我的 C 知识很老,所以我不确定字符串是什么。无论哪种方式,它都是有帮助的,但并非绝对需要有一个很好的预置零缓冲区来读取文件的内容。因此,无论您是归零word还是执行以下操作,请先将输入归零。

#define IN_BUF_LEN 120
char in_buf[IN_BUF_LEN] = {0};

120 个字符是一个安全的大小,假设您的大部分文本行的长度约为 80 个字符或更少。

其次,您基于 a 的值循环strcmp而不是实际读取文件。它可能会完成同样的事情,但我会while以到达文件结尾为基础。

最后,您声明duh了一个指针,而不是一个存储fgets返回值的地方。这也是个问题。所以,duh应该与in_buf上面类似地声明。

最后,您argv[1]在编译时分配值,而不是运行时。我看不出你在哪里得到你想要的。如果您声明temp为指针然后分配argv[1]给它,您将只有另一个指向 的指针argv[1],但实际上并未将值复制argv[1]到局部变量。为什么不直接使用argv[1]

于 2012-11-18T21:57:48.643 回答