代码之家  ›  专栏  ›  技术社区  ›  EGHDK asteri

为什么C在一个小字符数组中包含一个大字符串?[已关闭]

c
  •  -2
  • EGHDK asteri  · 技术社区  · 10 年前

    免责声明:做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. 为什么“第一件事”会打印出str2?
    2. 为什么str2的第一个字母被切掉了?
    3. 为什么“第二件事:”不能打印出来?
    4. 我制作了大小为1的char数组,它不应该只包含1个字母吗?
    4 回复  |  直到 10 年前
        1
  •  7
  •   Dolda2000    10 年前

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

    1. 为什么“第一件事”会打印出str2?
    2. 为什么str2的第一个字母被切掉了?

    您已为两个分配了存储空间 char s在堆栈上。编译器将它们彼此相邻地分配 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
    

    然后,在第二个 扫描 ,内存的同一部分将如下所示:

        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
    

    换句话说,第二个输入只会覆盖第一个输入,因为它超出了分配给它的存储空间的界限。然后,当你打印出来 结构1 ,它只打印地址为 结构1 ,如上图所示 mallertext .

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

    这是因为两个相互作用的效应。首先,你在哪里打印 结构2 ,您不会以换行符结束输出。 stdout 通常是行缓冲的,这意味着写入它的数据实际上不会写入底层终端,直到A)写入新行,B)显式调用 fflush(stdout) 或C)程序退出。

    因此,它会在程序退出时打印它,但您的程序永远不会退出。由于您覆盖了堆栈中未管理的部分,因此在本例中,您覆盖了 main ,因此,当您从 主要的 ,您的程序会立即崩溃,因此永远不会达到刷新的程度 标准输出 .

    对于您的程序 主要的 如下所示(假设AMD64 Linux):

    RBP+8: Return address
    RPB+0: Previous frame address
    RBP-1: str1
    RBP-2: str2
    

    自从 ahugestatement 包括其 NUL 终止符是15个字节,其中14个字节不适合 结构1 覆盖整个前一帧地址和6字节的返回地址。由于新的返回地址完全无效,所以当从main返回的地址跳转到内存中甚至没有映射的地址时,您的程序会出现分段错误。

    1. 我制作了大小为1的char数组,它不应该只包含1个字母吗?

    是的,确实如此。这只是你抹掉了它后面的记忆。

    一般来说, 扫描 如果你想做任何最基本的检查非法输入的形式,这并不是一个非常有用的功能。如果你真的希望进行交互式输入,那么使用以下方法几乎总是更好的 fgets() 然后解析读取的输入。 fgets() 不像 扫描 ,为接收缓冲区的大小接受一个额外的输入,然后将确保不在其外部写入。

        2
  •  2
  •   neerad29    10 年前

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

        3
  •  2
  •   Thilo    10 年前

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

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

        4
  •  2
  •   Andreas DM    10 年前

    您的数组只支持 一 字符,其余部分超出边界。

    超出数组范围的访问是 未定义 通常是灾难性的。