为什么程序访问指针的非法指针不会崩溃?

访问指针非法指针的程序不会因SIGSEGV而崩溃。 这不是一件好事,但我想知道这可能是什么以及这个过程如何在生产中存活了很多天。 这对我来说很困惑。

我已经将这个程序用于Windows,Linux,OpenVMS和Mac OS,他们从未抱怨过。

#include  #include  void printx(void *rec) { // I know this should have been a ** char str[1000]; memcpy(str, rec, 1000); printf("%*.s\n", 1000, str); printf("Whoa..!! I have not crashed yet :-P"); } int main(int argc, char **argv) { void *x = 0; // you could also say void *x = (void *)10; printx(&x); } 

缺乏内存故障我并不感到惊讶。 该程序没有取消引用未初始化的指针。 相反,它是从指针变量开始复制和打印内存的内容,以及超出它的996(或992)个字节。

由于指针是一个堆栈变量,因此它会在堆栈顶部附近打印内存。 该内存包含main()的堆栈帧:可能是一些保存的寄存器值,一个程序参数计数,一个指向程序参数的指针,一个指向环境变量列表的指针,以及一个保存的指令寄存器,用于返回main() ,通常在C运行时库启动代码中。 在我调查的所有实现中,下面的堆栈帧包含环境变量本身的副本,指向它们的指针数组,以及指向程序参数的指针数组。 在Unix环境中(您提示您正在使用),程序参数字符串将低于该环境。

所有这些存储器都是“安全”的打印,除了会出现一些可打印的字符,这可能会弄乱显示终端。

主要的潜在问题是是否有足够的堆栈内存分配和映射以防止访问期间的SIGSEGV。 如果环境数据太少,可能会发生段故障 。 或者,如果实现将该数据放在别处,那么这里只有几个堆栈的单词。 我建议通过清除环境变量并重新运行程序来确认。

如果任何C运行时约定不正确,则此代码不会如此无害:

  • 该体系结构使用堆栈
  • 在堆栈上分配局部变量( void *x
  • 堆栈朝向编号较低的内存增长
  • 参数在堆栈上传递
  • 是否使用参数调用main() 。 (某些轻型环境,如嵌入式处理器,不带参数调用main() 。)

在所有主流的现代实现中,所有这些通常都是正确的。

非法内存访问是未定义的行为 。 这意味着您的程序可能会崩溃,但不能保证,因为确切的行为是未定义的

(开发人员之间的一个笑话,特别是当面对那些不注意这些事情的同事时,“调用未定义的行为可能会格式化你的硬盘,它只是不能保证”。;-))

更新:这里有一些热门讨论。 是的,系统开发人员应该知道给定系统上 实际发生什么。 但是这些知识与CPU,操作系统,编译器等有关,并且通常用途有限,因为即使你使代码工作,它的质量仍然很差。 这就是为什么我把答案局限于最重要的一点,并且问了实际的问题(“为什么不崩溃”):

问题中发布的代码没有明确定义的行为,但这只是意味着你不能真正依赖它的作用,而不是它应该崩溃。

如果取消引用无效指针,则调用未定义的行为。 这意味着,程序可以崩溃,它可以工作,它可以煮一些咖啡,无论如何。

当你有

 int main(int argc, char **argv) { void *x = 0; // you could also say void *x = (void *)10; printx(&x); } 

您将x声明为值为0的指针,并且该指针位于堆栈中,因为它是一个局部变量。 现在,您将传递给printx x地址 ,这意味着

 memcpy(str, rec, 1000); 

您正在从堆栈上方(或实际上从堆栈本身)复制数据到堆栈(因为堆栈指针地址在每次推送时都会减少)。 源数据可能由同一页表条目覆盖,因为您只复制1000个字节,因此不会出现分段错误。 但是,最终,正如已经写的那样,我们正在讨论未定义的行为。

如果你写信给未被禁止的区域,它很可能会崩溃。 但是你正在阅读 ,它可以。 但这种行为仍未定义。