6

所以我正在浏览我的 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

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

4

7 回答 7

5

以下是我对它的理解:

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

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

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

于 2012-11-07T03:55:18.083 回答
5

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

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

不过,出于日常普通程序的目的,编写C程序的目的是您需要并且不应该关心变量的存储位置。

于 2012-11-07T04:03:00.307 回答
3

你不能。

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

于 2012-11-07T03:56:24.400 回答
3

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

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

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

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

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

于 2012-11-07T05:02:29.547 回答
1

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

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

于 2012-11-07T04:37:16.813 回答
1

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

于 2020-01-11T04:01:15.887 回答
0

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

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

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

于 2012-11-07T04:26:56.047 回答