您应该对三种类型的标准行为感兴趣。
1/定义的行为。这将适用于所有符合要求的实现。自由地使用它。
2/实现定义的行为。如前所述,它取决于实现,但至少它仍然是定义的。实现需要记录他们在这些情况下所做的事情。如果您不关心可移植性,请使用它。
3/未定义的行为。任何事情都有可能发生。我们的意思是任何事情,包括你的整个计算机崩溃成一个赤裸裸的奇点并吞噬自己,你和你的大部分同事。永远不要使用这个。曾经!严重地!不要让我过来。
将超过 4 个字符和一个零字节复制到 achar[5]
是未定义的行为。
说真的,你的程序为什么会因为 14 个字符而不是 13 个字符而崩溃并不重要,你几乎肯定会覆盖堆栈上的一些非崩溃信息,并且你的程序很可能会产生不正确的结果。事实上,崩溃更好,因为至少它可以阻止你依赖可能的不良影响。
将数组的大小增加到更合适的大小(char[14]
在这种情况下使用可用信息)或使用其他一些可以处理的数据结构。
更新:
由于您似乎非常关心为什么额外的 7 个字符不会导致问题,但 8 个字符会导致问题,让我们设想一下输入时可能的堆栈布局main()
。我说“可能”,因为实际布局取决于编译器使用的调用约定。由于 C 启动代码main()
使用argc
and调用,因此在为 a 分配空间后argv
,位于 开头的堆栈可能如下所示:main()
char[5]
+------------------------------------+
| C start-up code return address (4) |
| argc (4) |
| argv (4) |
| x = char[5] (5) |
+------------------------------------+
当您使用以下内容写入字节Hello1234567\0
时:
strcpy (x, "Hello1234567");
to x
,它会覆盖argc
andargv
但是,在从 回来时main()
,没关系。专门Hello
填充x
、1234
填充argv
和567\0
填充argc
。如果您实际上没有尝试使用 argc
和/或argv
之后,您会没事的:
+------------------------------------+ Overwrites with:
| C start-up code return address (4) |
| argc (4) | '567<NUL>'
| argv (4) | '1234'
| x = char[5] (5) | 'Hello'
+------------------------------------+
但是,如果您将Hello12345678\0
(注意额外的“8”)写入x
,它会覆盖返回地址的argc
andargv
和1 个字节,因此,当main()
尝试返回 C 启动代码时,它会进入仙境:
+------------------------------------+ Overwrites with:
| C start-up code return address (4) | '<NUL>'
| argc (4) | '5678'
| argv (4) | '1234'
| x = char[5] (5) | 'Hello'
+------------------------------------+
同样,这完全取决于编译器的调用约定。有可能不同的编译器总是将数组填充为 4 个字节的倍数,并且在您编写另外三个字符之前,代码不会在那里失败。即使是同一个编译器也可能在堆栈帧上分配不同的变量,以确保满足对齐要求。
这就是他们所说的未定义的意思:你不知道会发生什么。