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

时间:2016-02-26 02:39:15

标签: 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);
}

这是程序流程:

whattup
Enter something: ahugestatement
Enter something else: smallertext
first thing: mallertext

我不明白的事情:

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

4 个答案:

答案 0 :(得分:7)

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

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

您已在堆栈上为两个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

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

这是因为两种效果相互作用。首先,在您打印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. 我制作了一个大小为1的char数组,不应该只有1个字母吗?
  2.   

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

作为一般性陈述,scanf如果你想要做任何最基本的非法输入检查,那么它并不是一个非常有用的功能。如果您希望完全使用交互式输入,那么使用fgets()之类的内容几乎总是更好,然后解析读取输入。与fgets()不同,scanf对接收缓冲区的大小进行额外输入,然后确保不在其外部写入。

答案 1 :(得分:2)

您必须在C中进行边界检查,以确保缓冲区不会溢出。所以你的输出是未定义的。如果你多次运行该代码,它会在某些时候崩溃,因为溢出的缓冲区最终会覆盖重要的东西。

答案 2 :(得分:2)

这称为缓冲区溢出。你分配了一个字符来保存你的输入,但是你正在写的那个(弄乱你的其余程序的内存)。

与Java不同,C编译器和运行时不强制执行数组边界。这是&#34;(内存)管理语言与#34;之间的主要区别之一。和低级语言。

答案 3 :(得分:2)

您的数组只包含一个字符,其余字符超出范围。

访问数组的范围 undefined ,通常是灾难性的。