0

我知道下面的代码是不正确的,因为我正在为任意地址赋值。

#include <iostream>

using namespace std;

int main()
{
int *i;
*i = 12;     // Not right.... i is not initialized.
cout << *i << endl;
return 0;
}

这段代码在 Linux 上给出了分段错误。但是,在 Windows 上它输出 12...

为什么它可以在 Windows 上运行?我不是将 12 分配给我的程序没有特权的任意位置吗?

4

3 回答 3

12

它实际上调用了未定义的行为。所以这两种行为都是正确的,即它可能会给出段错误,也可能不会。

下次运行时,Windows 版可能会崩溃,Linux 版可能会向您发送威胁邮件!

换句话说,任何事情都可能发生。C++ 规范和(符合标准的)编译器都没有提供任何保证,这就是 C++ 规范将这种行为称为未定义行为的原因。

于 2012-06-07T17:11:09.630 回答
2

因为恰好i在入栈位置的随机数据在Linux下恰好形成了不可写地址,而在Windows下恰好包含了可写地址。

但是请注意,您无法确定代码对 Windows 的其他影响。可能是,如果您碰巧在 13 日星期五下午 6:16 开始该程序,那么该地址恰好是存储“输出”的系统调用号的地址,并且可能恰好12对“删除文件”,这可能删除您拥有的最重要的文件。是的,这是极不可能的,但并非完全不可能(如果您使用特定编译器设置使用特定编译器的特定版本编译它,它可能只会发生)。

或者简而言之,没有人可以确切地告诉您运行此代码时会发生什么(请注意,即使在 Linux 下,如果您更改编译器选项、编译器版本或碰巧使用命令行参数调用它,行为也可能会有所不同...... )

于 2012-06-07T17:22:20.070 回答
1

@Nawaz 和 @celtschk 是对的。您的代码确实调用了未定义的行为,因此这两种行为都是正确的。不过,您看到这种特殊的未定义行为是有原因的。原因值得理解。

当操作系统在运行时加载程序时,它会为它打开一个虚拟地址空间。 在 64 位系统上,虚拟空间从地址 0 延伸到地址 0xffffffffffffffff。

您的系统自然只有一小部分实际物理内存,它需要填充如此巨大的地址空间。此外,您的系统必须在可能同时运行的几个不同进程之间共享它所拥有的内存——每个进程都有自己的私有虚拟地址空间。

所以这就是系统的作用。它最初为您新加载的程序分配一小块内存——可能是 4 kiB——并将其映射到地址空间的一小段,如 0xffffffffffffff000 到 0xffffffffffffffff。你的程序在这个空间中构建了一堆存储的数据对象。如果堆栈增长超过 4 kiB,系统将分配另一个 4 kiB,从 0xffffffffffffe000 到 0xffffffffffffefff,并且将以对正在运行的程序透明的方式进行分配。

只要程序以一种有序的、规定的方式构建和使用它的堆栈,它就好像它不知道对其堆栈大小的任何限制一样运行。但是,如果程序以无序的方式访问内存,那么结果取决于几个因素,但如果访问的是系统从未分配过程序实际内存的地址,肯定会引发系统异常利用。

现在,这个答案遗漏了很多细节。您可能知道还有第二种内存,即动态内存池。 还有其他因素,包括“磁盘交换”。但是,如果指针i碰巧指向程序已经出于某种目的使用的内存,则不会引发系统异常。它指向这样的记忆吗?很难说。显然,在您的情况下,它似乎在一个系统上这样做,但在另一个系统上却没有。

顺便说一句,我与@celtschk 在一点上略有不同。现代 CPU 和操作系统是专门为防止系统灾难而构建的,就像他所说的那样。C 和 C++ 标准不保证不会发生此类灾难,但操作系统会保证,除非程序以超级用户权限运行(即使这样,也可能存在部分有效的保护措施)。我认为您不必担心使用普通的用户模式程序无意中擦除磁盘。不过,在嵌入式系统上,这是另一回事,@celtschk 是完全正确的。

于 2012-06-07T17:31:46.527 回答