【问题标题】:How do you know the exact address of a variable?你怎么知道变量的确切地址?
【发布时间】:2012-11-07 03:51:12
【问题描述】:

所以我正在翻阅我的 C 编程教科书,我看到了这段代码。

#include <stdio.h>

int j, k;
int *ptr;

int main(void)
{
    j = 1;
    k = 2;
    ptr = &k;
    printf("\n");
    printf("j has the value %d and is stored at %p\n", j, (void *)&j);
    printf("k has the value %d and is stored at %p\n", k, (void *)&k);
    printf("ptr has the value %p and is stored at %p\n", (void *)ptr, (void *)&ptr);
    printf("The value of the integer pointed to by ptr is %d\n", *ptr);

    return 0;
}

我运行它,输出是:

j 的值为 1,存储在 0x4030e0

k 的值为 2,存储在 0x403100

ptr 的值为 0x403100,存储在 0x4030f0

ptr指向的整数的值为2

我的问题是,如果我没有通过编译器运行它,你怎么能通过查看这段代码知道这些变量的地址?我只是不确定如何获取变量的实际地址。谢谢!

【问题讨论】:

  • 变量在内存中的布局方式在某种程度上是操作系统的决定。与此相反,数组是顺序的。但是,实际地址永远无法保证。
  • 另外,如果你多次执行同一个程序,几乎可以保证每次执行都会得到不同的地址。对于正常变量,您会得到相同的结果,这将是一个极端的巧合。

标签: c pointers memory-address


【解决方案1】:

这是我的理解:

C 中内存中事物的绝对地址是未指定的。它没有标准化为语言。正因为如此,你不能只看代码就知道事物在内存中的位置。 (但是,如果使用相同的编译器、代码、编译器选项、运行时和操作系统,地址可能是一致的。)

当您开发应用程序时,这不是您应该依赖的行为。但是,在某些情况下,您可能会依赖两个事物的位置之间的差异。例如,您可以确定两个数组元素的指针地址之间的差异,以确定它们相隔多少个元素。

顺便说一句,如果您正在考虑使用变量的内存位置来解决特定问题,您可能会发现发布一个单独的问题来询问如何在不依赖此行为的情况下这样做会很有帮助。

【讨论】:

  • 地址不是实现定义的。它们只是未指定。不同之处在于第一个必须记录在案,第二个不需要。
【解决方案2】:

除了用“%p”打印变量之外,没有其他方法可以“知道”标准 C 中变量的确切地址。实际地址由许多不受程序员编写代码控制的因素决定。这是操作系统、链接器、编译器、使用的选项以及可能其他的问题。

也就是说,在嵌入式系统世界中,有一些方法可以表示这个变量必须驻留在这个地址,例如,如果外部设备的寄存器映射到地址空间 em> 正在运行的程序。这通常发生在所谓的链接器文件映射文件中,或者通过将整数值分配给指针(使用强制转换)。所有这些方法都是非标准的。

不过,就您日常的普通程序而言,编写 C 程序的目的是您需要并且不应该关心变量存储在哪里。

【讨论】:

    【解决方案3】:

    你不能。

    不同的编译器可以将变量放在不同的位置。在某些机器上,地址不是一个简单的整数。

    【讨论】:

      【解决方案4】:

      编译器只知道“第三个整数全局变量”和“从堆栈指针向下分配 36 个字节的四个字节”。它仅在相对术语中指全局变量、指向子程序(函数)的指针、子程序参数和局部变量。 (不要介意 C++ 中多态对象的额外内容,哎呀!)这些相对引用作为特殊代码和偏移值保存在对象文件(.o 或 .obj)中。

      链接器可以填写一些细节。在连接多个目标文件时,它可能会修改其中一些粗略的位置参考。当来自多个编译单元的全局变量合并时,全局变量位置将共享一个空间(数据部分);链接器决定它们的顺序,但仍将它们描述为相对于整个全局变量集的开始。结果是一个带有最终操作码的可执行文件,但地址仍然是粗略的并且基于相对偏移量。

      直到可执行文件被加载,加载器才会将所有相对地址替换为实际地址。现在这是可能的,因为加载器(或它所依赖的操作系统的某些部分)决定在进程的整个虚拟地址空间中存储程序操作码(文本部分)、全局变量(BSS、数据部分)和调用堆栈和其他东西。加载器可以进行数学运算,并将实际地址写入可执行文件中的每个位置,通常作为“加载立即”操作码和所有涉及内存访问的操作码的一部分。

      谷歌“重定位表”了解更多信息。有关特定平台的更详细说明,请参阅 http://www.iecc.com/linker/linker07.html(有些旧)。

      在现实生活中,虚拟地址通过虚拟内存系统映射到物理地址,使用段或其他机制将每个进程保持在单独的地址空间中,这一切都变得复杂。

      【讨论】:

        【解决方案5】:

        我想进一步建立在已经提供的答案的基础上,指出一些编译器,例如 Visual Studio 的,具有称为地址空间布局随机化 (ASLR) 的功能,它使程序从随机内存地址开始作为反- 病毒功能。鉴于您在输出中拥有的地址,我会说您在没有它的情况下编译(我认为没有它的程序从地址 0x400000 开始)。此信息的来源是this question 的回复。

        也就是说,编译器决定了存储局部变量的内存地址。地址很可能会随着编译器的不同而改变,也可能会随着源代码的每个版本而改变。

        【讨论】:

          【解决方案6】:

          每个进程都有自己的逻辑地址空间,从零开始。您的程序可以访问的收件人都相对于零。任何内存位置的绝对地址只有在将进程加载到主内存后才能确定。这是通过现代操作系统使用动态重定位来完成的。因此,每次将进程加载到内存中时,都可能根据内存的可用性将其加载到不同的位置。因此,允许用户进程知道存储在内存中的数据的确切地址没有任何意义。您的代码打印的是逻辑地址,而不是确切的或物理地址。

          【讨论】:

            【解决方案7】:

            继续上面描述的答案,请不要忘记进程将在自己的虚拟地址空间中运行(进程隔离)。这可以确保当您的程序损坏某些内存时,其他正在运行的进程不会受到影响。

            进程隔离: http://en.wikipedia.org/wiki/Process_isolation

            进程间通信 http://en.wikipedia.org/wiki/Inter-process_communication

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-02-10
              • 1970-01-01
              • 1970-01-01
              • 2011-08-23
              • 2010-12-29
              • 1970-01-01
              • 1970-01-01
              • 2021-08-18
              相关资源
              最近更新 更多