19

访问非法指针的程序不会因 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);
}
4

5 回答 5

29

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

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

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

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

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

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

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

于 2013-07-25T08:13:00.353 回答
17

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

(开发人员之间的一个玩笑,尤其是在面对对此类事情粗心的同事时,是“调用未定义的行为可能会格式化您的硬盘驱动器,但不能保证这样做”。;-))

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

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

于 2013-07-25T07:59:25.060 回答
9

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

于 2013-07-25T08:00:38.070 回答
4

当你有

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

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

memcpy(str, rec, 1000);

您正在将数据从堆栈上方(或实际上从堆栈本身)复制到堆栈(因为堆栈指针地址在每次推送时都会减少)。当您仅复制 1000 个字节时,源数据可能被相同的页表条目覆盖,因此您不会遇到分段错误。然而,最终,正如已经写过的,我们谈论的是未定义的行为。

于 2013-07-25T08:21:35.543 回答
2

如果你写到未访问的区域,它很可能会崩溃。但你正在阅读,它可以是好的。但是行为仍然是未定义的。

于 2013-07-25T08:02:27.870 回答