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

pav*_*ala 19 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);
}
Run Code Online (Sandbox Code Playgroud)

wal*_*lyk 29

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

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

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

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

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

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

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

  • @DevSolar,纯粹学术性的是说,所发生的事情是未定义的行为,没有给出实际观察到的行为的解释.我认为能够解释机器在任何给定情况下的作用是好的工程和良好的计算机科学,即使在语言规范将某些决策的责任留给编译器和操作系统的实现者时也是如此.我们的程序不是在真实问题的真空中执行. (11认同)
  • @Joni:所以你认为这是一个很好的解释说指针是32或64位,参数在堆栈上传递,堆栈向下延伸,有一个指向环境变量的指针和`argc` /`argv`,返回地址保存在堆栈上,yadda yadda,*甚至没有将该声明限定为"......在Linux和Windows上"?*幸福地假设**在目标机器上有**像`SIGSEGV`这样的东西,并且这是非法的访问不会崩溃整个操作系统?对于这些陈述中的每一个,我都知道这个假设不会成立的系统. (2认同)

Dev*_*lar 17

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

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

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

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

  • @wallyk:同样,这个代码可能适用于机器A和机器B,A和B一起可能占全球所有系统的99%,但OP发布的代码确实调用*undefined*行为,*定义*作为"没有语言标准定义的行为".因为它没有定义,谨慎的开发人员永远不应该依赖于系统X*上实际发生的事情,因为它*会在某一天中断.我尊重你的实践经验,但是我强烈地感觉到太多有抱负的编码器被"引擎盖下"的东西显得太多了,太少了"不要触摸这个"警告. (3认同)

Dev*_*lus 9

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