【问题标题】:Why the address of variables is identical even across reboot?为什么即使在重新启动后变量的地址也是相同的?
【发布时间】:2020-12-29 20:42:03
【问题描述】:

我试过这个程序来了解变量是如何存储的并且有疑问。

我编译了上面的代码并创建了一个可执行文件,我记下了输出(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

【问题讨论】:

  • @Yunnosch 鉴于该问题已被编辑,并且您的答案似乎涵盖得很好,我认为没有必要发表新评论——我可以为您贡献的东西不多尚未涵盖。

标签: c variables memory-address


【解决方案1】:

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

当我修改变量的值并重新执行代码时。我发现与以前相同的地址被分配给变量。

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

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

这不能保证(但它可能发生,不仅是两个随机的 64 位整数碰巧相同)。
即使您在两者之间重新启动计算机。
即使您在两者之间删除并重建可执行文件。
即使两个程序(或同一程序的两个副本)同时运行。
即使这些变量在每个程序中具有不同的值。
即使它们在每个程序中都有不同的类型。
即使这两个输出来自同一个程序的两个进程,并且在同一个地址的两个不同类型的变量中具有不同的值。

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

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

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

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

伪代码:

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

你应该会看到不同的地址,不同的值或相同的值无关紧要。

【讨论】:

    【解决方案2】:

    我试过这个程序来了解变量是如何存储的并且有疑问。

    Automatic variables通常call stackprocessor registers中。

    我对 C 标准的理解(参见例如 n2176 ...)是一个非常聪明的编译器可以在编译时优化你的 printf。你可以编写一个GCC plugin 这样做(另见this 草稿报告)。您的编译器插件可以在编译时将您的 printf 语句替换为接近 puts("0x1000, 0x1008, 0x2000, 0x2008"); 的内容,并且仍然符合 C 标准。

    为什么重启后变量的地址还是一样的?

    在 Linux/x86-64 上(例如,一些 Debian 具有默认设置,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 % 是我的zsh shell 提示符。

    解释与ASLR有关。在 Linux 上,使用 proc(5)strace(1)gdb(1)pmap(1) 来了解 address spaceprocess

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

    【讨论】:

    • 我在我的答案的早期版本中尝试了“你的观察不太可能”的方法。然而,OP 非常坚决地讨论他们肯定会观察到的东西......
    猜你喜欢
    • 1970-01-01
    • 2022-11-23
    • 2016-02-20
    • 1970-01-01
    • 2020-09-05
    • 2021-03-19
    • 1970-01-01
    • 2020-02-06
    • 2021-12-02
    相关资源
    最近更新 更多