为什么C在一个小字符数组中保存一个大字符串?

EGH*_*HDK -2 c

免责声明:已经做了一段时间的Java,但对C来说是新手.

我有一个我写的程序,我故意试图看看不同的输入和输出会发生什么.

#include <stdio.h>

int main() {

    printf("whattup\n");
    char str1[1], str2[1];

    printf("Enter something: ");
    scanf("%s", &str1);

    printf("Enter something else: ");
    scanf("%s", &str2);

    printf("first thing: %s\n", str1);
    printf("second thing: %s", str2);
}
Run Code Online (Sandbox Code Playgroud)

这是程序流程:

whattup
Enter something: ahugestatement
Enter something else: smallertext
first thing: mallertext
Run Code Online (Sandbox Code Playgroud)

我不明白的事情:

  1. 为什么"第一件事"打印出str2?
  2. 为什么str2有第一个字母被截断?
  3. 为什么"第二件事:"不打印出来?
  4. 我制作了一个大小为1的字符数组,不应该只有1个字母吗?

Dol*_*000 7

要具体回答您的问题,您必须记住,所发生的事情非常具体.您所看到的特定行为不一定适用于所有C实现.这就是C标准所称的"未定义行为".考虑到这一点:

  1. 为什么"第一件事"打印出str2?
  2. 为什么str2有第一个字母被截断?

您已char在堆栈上分配了两个存储空间.编译器分配它们彼此相邻,与str2前面str1在存储器中.因此,在您的第一个之后scanf,堆栈的一部分将如下所示:

    str1 is allocated here
    v
?   a   h   u   g   e   s   t   a   t   e   m   e   n   t   \0
^
str2 is allocated here
Run Code Online (Sandbox Code Playgroud)

然后,在第二个之后scanf,相同的内存部分将如下所示:

    str1 is allocated here
    v
s   m   a   l   l   e   r   t   e   x   t   \0  e   n   t   \0
^
str2 is allocated here
Run Code Online (Sandbox Code Playgroud)

换句话说,第二个输入只是覆盖第一个输入,因为它超出了为其分配的存储的范围.然后,当你打印出来时str1,它只是打印地址上的任何str1内容,正如你在上图中所看到的那样mallertext.

  1. 为什么"第二件事:"不打印出来?

这是因为两种效果相互作用.首先,在您打印的位置str2,您不会使用换行符结束输出.stdout通常是行缓冲的,这意味着写入它的数据实际上不会写入底层终端,直到A)写入换行符,B)显式调用fflush(stdout),或C)程序退出.

因此,它会在程序退出时打印出来,但程序永远不会退出.由于您覆盖了不管理的堆栈部分,在这种情况下,您将覆盖返回地址main,因此,当您返回时main,程序会立即崩溃,因此永远不会到达它将刷新的位置stdout.

对于您的程序,堆栈框架布局main看起来像这样(假设AMD64 Linux):

RBP+8: Return address
RPB+0: Previous frame address
RBP-1: str1
RBP-2: str2
Run Code Online (Sandbox Code Playgroud)

由于ahugestatement包含其NUL终结符的是15个字节,因此不适合的14个字节会str1覆盖整个前一个帧地址和6个字节的返回地址.由于新的返回地址完全无效,因此当从main跳转到甚至没有映射到内存中的地址时,程序会发生段错误.

  1. 我制作了一个大小为1的字符数组,不应该只有1个字母吗?

是的,确实如此.只是你破坏了它后面的记忆.

作为一般性陈述,scanf如果您想要进行任何最基本的非法输入检查,那么它实际上并不是非常有用的功能.如果您希望完全使用交互式输入,那么使用类似的东西几乎总是更好fgets(),然后解析读取输入.fgets()不像scanf,接受额外的输入,接收缓冲区的大小,然后将确保不在外面写.