字符数组的空终止符问题

43
考虑以下情况:
#include<stdio.h>
int main()
{
    char A[5];
    scanf("%s",A);
    printf("%s",A);
}
我的问题是,如果字符数组 A[5] 只包含两个字符(比如 "ab"),那么 A[0]='a'A[1]='b' 并且 A[2]='\0'。 但如果输入是 "abcde",那么在这种情况下 '\0' 在哪里?A[5] 是否包括 '\0'? 如果是,为什么? sizeof(A) 将始终返回 5。那么当数组已满时,是否有一个额外的字节保留用于 '\0',而 sizeof() 不计算它?
8个回答

64

如果您输入的字符数超过四个,则额外的字符和空终止符将被写在数组末尾之外,覆盖不属于该数组的内存。这就是缓冲区溢出。

C语言无法阻止您破坏您不拥有的内存。这会导致未定义行为。您的程序可以做任何事情-它可能会崩溃,可能会静默地破坏其他变量并导致混乱的行为,也可能是无害的,或者其他任何结果。请注意,并没有保证您的程序可靠地工作或可靠地崩溃。您甚至不能依赖它立即崩溃。

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

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

if (fgets(A, sizeof A, stdin) == NULL) {
    /* error reading input */
}
函数会让数组末尾多出一个换行符 ('\n'),这可能会让人感到烦恼。因此你可能需要编写代码来将其去除。
size_t length = strlen(A);
if (A[length - 1] == '\n') {
    A[length - 1] = '\0';
}

呃,一个简单(但是有问题的)scanf("%s")变成了一个7行的怪物。这就是今天的第二个教训:C语言在输入输出和字符串处理方面并不擅长。虽然可以完成任务并确保安全,但是C语言会一直抗拒。


12

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

scanf("%4s", A);

为了从stdin读取最多4个字符。


5
在C语言中,字符数组实际上只是指向内存块的指针。如果您告诉编译器为字符保留5个字节,它就会这样做。如果您尝试在其中放置超过5个字节的内容,则会覆盖超出您预留的5个字节的内存部分。
这就是为什么C语言可以有严格的安全实现。您必须知道您将只写入4个字符+\0。C语言会允许您覆盖内存直到程序崩溃。
请不要将char foo[5]视为字符串。请将其视为放置5个字节的空间。您可以在其中存储5个字符而无需使用null,但必须记住需要进行memcpy(otherCharArray,foo,5)而不是使用strcpy。您还必须知道otherCharArray具有足够的空间来容纳这5个字节。

4
你最终会得到“未定义的行为”。
正如你所说,A的大小始终为5,因此如果读取5个或更多的字符,scanf将尝试写入不应修改的内存。
而且,没有保留空间/char用于\0符号。

4

任何长度大于4个字符的字符串都将导致scanf写入超出数组边界的位置。这样的行为是未定义的,如果你幸运的话,会导致程序崩溃。

如果你想知道为什么scanf不会停止写入太长以至于无法存储在数组A中的字符串,因为scanf无法知道sizeof(A)是5。当你把一个数组作为C函数的参数时,该数组会衰变为指向数组第一个元素的指针。因此,在函数内部无法查询数组的大小。

为了限制读入到数组中的字符数,请使用:

scanf("%4s", A);

3

在C语言中没有保留的字符,所以你必须小心,不要将整个数组填满到无法插入空字符终止符为止。Char函数依赖于空字符终止符,如果你发现自己处于你描述的情况下,你将得到灾难性的结果。

你会看到很多C代码将使用类似strncpy这样的“n”派生函数。从该手册页上可以阅读到:

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

strlen也依赖于空字符来确定字符缓冲区的长度。如果缺少该字符,则会得到不正确的结果。


0

空字符用于终止数组。它位于数组的末尾,表示数组在该点结束。数组会自动将最后一个字符设为空字符,以便编译器可以轻松理解数组已经结束。


4
数组不会自动将最后一个字符设为 null 字符,这并不正确。 - Brandin

-3

\0 是一个终止运算符,当数组已满时会自动终止。 如果数组未满,则 \0 将位于数组末尾。 当您输入字符串时,它将从数组末尾开始读取。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接