为什么变量的地址即使在重启后也是相同的?

Why the address of variables is identical even across reboot?

我试过这个程序,想知道变量是如何存储的,但有疑问。

我编译了上面的代码并创建了一个可执行文件,我记下了输出(a、b、c、d 的地址),然后我删除了可执行文件并重新启动了我的电脑,之后我修改了值变量并重新执行代码。我发现分配给变量的地址与以前相同。
这是我不理解的地方,因为变量存储在 RAM 中,而 RAM 是非常量。

怎么重启后地址还是一样?

#include <stdio.h>
int main(){
    int a=21, b=22, c=23, d=24;
    printf("%p, %p, %p, %p", &a, &b, &c, &d);
}

输出:

0xffffcbec, 0xffffcbe8, 0xffffcbe4, 0xffffcbe0

有点吹毛求疵,我这样做只是因为我怀疑不精确的措辞背后存在实际的误解,这可能会引起您的疑问。

when i modify the value of variables and re-execute the code. I find the same address is being assigned to variables as previous.

  • 修改现有变量的值并不会改变地址,就像让新家庭搬进大楼一样;那不会改变地址。
  • 地址没有分配给变量,值是。 (虽然分配给指针变量的值是地址,但这也不会改变指针变量的地址,只会改变它的值;就像旧房子里的一封信,上面写着以前住在那里的家庭的新地址,见“链表”,老房子的地址还是一样。
  • 全局变量的地址(与您的代码中的地址不同)在加载期间确定(在您为执行程序所做的任何操作之间,例如双击或键入 CLI 及其执行),可能与有意随机化,但也可能以确定性方式进行。
  • 局部变量的地址(如在您的代码中)是在调用它们和执行它们之间确定的(当时的模型是“堆栈框架设置”),也可能有意随机化,但也可能是确定性的

现在到了你想知道的核心,为什么你观察到变量的地址在很多情况下都是一样的。

这并不能保证(但它可能会发生,而不仅仅是两个随机的 64 位整数碰巧相同)。
即使你在中间重启电脑。
Eeven if you delete and rebuild the executables between.
即使两个程序(或同一程序的两个副本)同时运行.
即使这些变量在每个程序中具有不同的值。
即使他们在每个程序中都有不同的类型。
即使这两个输出来自同一个程序的两个进程,并且在同一地址的两个不同类型的变量中具有不同的值。

您的观察也具有误导性,因为在现代 PC 中,您输出的地址相同并不意味着变量在物理内存中位于同一位置。
现代软件的内存管理(也涉及硬件;查找“PMMU”)意味着他们对自己内存的看法(称为他们的虚拟地址space)与物理memory/RAM中发生的事情不同。

实际上有一些(与安全相关的)机制,我希望这些机制明确地尝试改变 location/address 变量从执行到执行你的程序,即上面提到的故意随机化。
这已经在 nanofarad 的评论中被称为“ASLR”,并在此处进行了描述:https://en.wikipedia.org/wiki/Address_space_layout_randomization
我希望它在当今几乎所有环境中的默认设置中都处于活动状态。因此,我怀疑在您的观察过程中有一些不寻常的配置在起作用。或者,也许您正在试验非常旧的编译器版本。

但是在很多情况下,地址相同这一事实并不意味着什么。如果那些提到的机制不起作用,那么相同的局部变量(在您的情况下,但在其他情况下也是全局变量)总是可以在相同的位置结束,即使您将测试代码移动到不同的函数并从 main(),甚至几次;只要您始终观察直接从 main() 输出或始终观察从被调用函数输出(否则见下文)。

如果我可以推测,您可以通过使用像您的 main 这样的子函数来更改您的程序进行实验,一个包装器子函数调用类似 main 的子函数,然后调用类似 main 的子函数和包装器函数main()。在那种情况下,您的局部变量应该位于两个不同的位置(C 标准实际上并未使用“堆栈”的概念,但这是我在这里指的模型)。

伪代码:

  • 主要()
    • 调用类似 main 的子函数
      • 输出局部变量的地址,就像你的代码
    • 调用包装器
      • 调用类似 main 的子函数
        • 输出局部变量的地址,就像你的代码

你应该看到不同的地址,不同的值或相同的值都没有关系。

I tried this program to know how variables are stored and have a doubt.

Automatic variables are usually on the call stack or in processor registers.

我对 C 标准的理解(参见 n2176 ...) is that a very clever compiler could optimize your printf at compile time. You could code a GCC plugin doing so (see also this 报告草案)。您的编译器插件可以在编译时将您的 printf 语句替换为接近 puts("0x1000, 0x1008, 0x2000, 0x2008"); 的内容,并且仍然符合 C 标准。

Why the address of variables is identical even across reboot?

关于Linux/x86-64(比如某些Debian with default settings, and GCC 10作为C编译器),上面的说法是错误的.

我编译了你的代码(在 /tmp/prasoon.c 中,在你的 printf 格式控制字符串的末尾添加了一个 \n ...)作为 gcc -Wall -g -O /tmp/prasoon.c -o /tmp/prasoon)并且我做到了运行 三遍:

rimski.x86_64 ~ 9:16 .0 % /tmp/prasoon
0x7ffdd8cfa2b8, 0x7ffdd8cfa2bc, 0x7ffdd8cfa2c0, 0x7ffdd8cfa2c4
rimski.x86_64 ~ 9:16 .0 % /tmp/prasoon
0x7ffc6d4599e8, 0x7ffc6d4599ec, 0x7ffc6d4599f0, 0x7ffc6d4599f4
rimski.x86_64 ~ 9:16 .0 % /tmp/prasoon
0x7fff288e5168, 0x7fff288e516c, 0x7fff288e5170, 0x7fff288e5174

注意上面的rimski.x86_64 ~ 9:16 .0 %是我的zshshell提示。

说明与ASLR. On Linux, use proc(5), strace(1), gdb(1), pmap(1) to understand the address space of your process有关。

在其他操作系统上,您可以阅读它们的文档以了解如何查询给定进程的地址 space。