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

时间:2013-07-25 07:57:23

标签: c undefined-behavior

访问指针非法指针的程序不会因SIGSEGV而崩溃。这不是一件好事,但我想知道这是怎么回事以及这个过程如何在生产中存活很多天。这让我感到很困惑。

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

#include <stdio.h>
#include <string.h>

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);
}

5 个答案:

答案 0 :(得分:29)

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

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

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

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

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

  • 架构使用堆栈
  • 在堆栈上分配局部变量(main()
  • 堆栈朝着编号较低的内存增长
  • 参数在堆栈上传递
  • 是否使用参数调用void *x。 (某些轻载环境,如嵌入式处理器,在没有参数的情况下调用main()。)

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

答案 1 :(得分:17)

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

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

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

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

答案 2 :(得分:9)

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

答案 3 :(得分:4)

当你有

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个字节,因此不会出现分段错误。然而,最终,正如已经写的那样,我们正在讨论未定义的行为。

答案 4 :(得分:2)

如果你到未开垦的地区,它很可能会崩溃。但是你正在阅读,它可以。但这种行为仍未定义。