40

考虑以下情况:

#include<stdio.h>
int main()
{
    char A[5];
    scanf("%s",A);
    printf("%s",A);
}

我的问题是 char 是否A[5]只包含两个字符。说“ab”,A[0]='a'然后A[1]='b'A[2]='\0'。但是如果输入是“abcde”,那么'\0'在这种情况下是哪里。将A[5]包含'\0'? 如果是,为什么? sizeof(A)将始终返回 5 作为答案。那么当数组已满时,是否有一个额外的字节保留'\0'sizeof()计算在内?

4

8 回答 8

60

如果您键入的字符超过四个,那么多余的字符和空终止符将被写入数组末尾之外,从而覆盖不属于数组的内存。这是缓冲区溢出。

C 不会阻止你破坏你不拥有的内存。这会导致未定义的行为。你的程序可以做任何事情——它可能会崩溃,它可能会默默地丢弃其他变量并导致令人困惑的行为,它可能是无害的,或者其他任何事情。请注意,不能保证您的程序将可靠地工作或可靠地崩溃。你甚至不能指望它立即崩溃。

这是一个很好的例子,说明为什么scanf("%s")是危险的,永远不应该使用。它不知道数组的大小,这意味着无法安全地使用它。相反,请避免 scanf 并使用更安全的东西,例如fgets()

fgets() 从流中最多读入一个小于 size的字符,并将它们存储到 s 指向的缓冲区中。在 EOF 或换行符后停止读取。如果读取了换行符,则将其存储到缓冲区中。终止的空字节 ('\0') 存储在缓冲区中的最后一个字符之后。

例子:

if (fgets(A, sizeof A, stdin) == NULL) {
    /* error reading input */
}

烦人的是, fgets() 将在数组末尾留下一个尾随换行符 ('\n')。因此,您可能还希望代码将其删除。

size_t length = strlen(A);
if (A[length - 1] == '\n') {
    A[length - 1] = '\0';
}

啊。一个简单的(但破碎的)scanf("%s")变成了一个 7 行的怪物。这是今天的第二课:C 不擅长 I/O 和字符串处理。可以做,也可以安全做,但是C会一直踢和尖叫。

于 2012-08-18T15:55:34.757 回答
10

正如已经指出的那样 - 您必须定义/分配一个长度为 N + 1 的数组才能正确存储 N 个字符。可以限制 scanf 读取的字符数量。在您的示例中,它将是:

scanf("%4s", A);

为了阅读最大值。来自标准输入的 4 个字符。

于 2012-08-18T16:01:17.060 回答
5

c 中的字符数组只是指向内存块的指针。如果您告诉编译器为字符保留 5 个字节,它会保留。如果您尝试在其中放置超过 5 个字节,它只会覆盖超过您保留的 5 个字节的内存。

这就是为什么 c 可以有严格的安全实现。你必须知道你只会写 4 个字符 + 一个 \0。C 将允许您覆盖内存,直到程序崩溃。

请不要将 char foo[5] 视为字符串。将其视为放置 5 个字节的位置。您可以在其中存储 5 个字符而不使用 null,但您必须记住您需要执行 memcpy(otherCharArray, foo, 5) 而不是使用 strcpy。您还必须知道 otherCharArray 有足够的空间容纳这 5 个字节。

于 2012-08-18T16:27:57.783 回答
4

你最终会得到未定义的行为

正如您所说,大小A始终为 5,因此如果您读取 5 个或更多chars,scanf将尝试写入不应修改的内存。

不,符号没有保留空间/字符\0

于 2012-08-18T15:54:37.217 回答
4

任何长度大于 4 个字符的字符串都会导致scanf写入超出数组的范围。产生的行为是未定义的,如果幸运的话,会导致程序崩溃。

如果你想知道为什么scanf不停止写入太长而无法存储在数组中的字符串,A那是因为无法scanf知道sizeof(A)是 5。当你将数组作为参数传递给 C 函数时,数组会衰减指向指向数组中第一个元素的指针。因此,无法在函数中查询数组的大小。

为了限制读入数组的字符数使用

scanf("%4s", A);
于 2012-08-18T16:01:35.393 回答
3

没有保留字符,因此您必须小心不要将整个数组填充到不能以空值终止的程度。Char 函数依赖于空终止符,如果您发现自己处于您所描述的情况,您将从它们那里得到灾难性的结果。

您将看到的许多 C 代码将使用函数的“n”个衍生物,例如 strncpy。从该手册页中,您可以阅读:

strcpy() 和 strncpy() 函数返回 s1。stpcpy() 和 stpncpy() 函数返回一个指向 s1 的终止 `\0' 字符的指针。如果 stpncpy() 没有以 NUL 字符终止 s1,它会返回一个指向 s1[n] 的指针(它不一定引用有效的内存位置。)

strlen 还依赖空字符来确定字符缓冲区的长度。如果并且当您缺少该字符时,您将得到不正确的结果。

于 2012-08-18T15:53:15.850 回答
-1

空字符用于终止数组。它位于数组的末尾,并显示数组在该点结束。数组自动使最后一个字符为空字符,以便编译器可以很容易地理解数组结束。

于 2014-02-07T16:51:08.030 回答
-3

\0 是一个终止符运算符,如果数组未满,则在数组已满时自行终止,那么当您输入一个字符串时,\0 将位于数组的末尾,它将从数组的末尾读取

于 2017-08-01T05:35:01.927 回答