免责声明:已经做了一段时间的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);
}
这是程序流程:
whattup
Enter something: ahugestatement
Enter something else: smallertext
first thing: mallertext
答案 0 :(得分:7)
要具体回答您的问题,您必须记住,所发生的事情非常具体。您所看到的特定行为不一定适用于所有C实现。这就是C标准所称的“未定义行为”。考虑到这一点:
- 为什么“第一件事”打印出str2?
- 为什么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
然后,在第二个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
换句话说,第二个输入只会覆盖第一个输入,因为它超出了为其分配的存储空间的范围。然后,当您打印出str1
时,它只会打印str1
地址处的内容,如上图所示,这是mallertext
。
- 为什么“第二件事:”不打印出来?
醇>
这是因为两种效果相互作用。首先,在您打印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
由于包含其ahugestatement
终结符的NUL
为15个字节,因此str1
中不适合的那些字节中的14个会覆盖整个前一个帧地址和返回地址的6个字节。由于新的返回地址完全无效,因此当从main跳转到甚至没有映射到内存中的地址时,程序会发生段错误。
- 我制作了一个大小为1的char数组,不应该只有1个字母吗?
醇>
是的,确实如此。只是你破坏了它后面的记忆。
作为一般性陈述,scanf
如果你想要做任何最基本的非法输入检查,那么它并不是一个非常有用的功能。如果您希望完全使用交互式输入,那么使用fgets()
之类的内容几乎总是更好,然后解析读取输入。与fgets()
不同,scanf
对接收缓冲区的大小进行额外输入,然后确保不在其外部写入。
答案 1 :(得分:2)
您必须在C中进行边界检查,以确保缓冲区不会溢出。所以你的输出是未定义的。如果你多次运行该代码,它会在某些时候崩溃,因为溢出的缓冲区最终会覆盖重要的东西。
答案 2 :(得分:2)
这称为缓冲区溢出。你分配了一个字符来保存你的输入,但是你正在写的那个(弄乱你的其余程序的内存)。
与Java不同,C编译器和运行时不强制执行数组边界。这是&#34;(内存)管理语言与#34;之间的主要区别之一。和低级语言。
答案 3 :(得分:2)
您的数组只包含一个字符,其余字符超出范围。
访问数组的范围 undefined ,通常是灾难性的。