C中变量的逻辑内存地址

西瓦姆·特里帕蒂(Shivam Tripathi)

考虑以下代码:

#include <stdio.h>
int main() {
  int a = 10;
  printf("%d %p\n", a, &a);
}

如果我反复编译并执行上述代码,它将为printf语句的地址部分打印不同的值

如果逻辑存储空间为16位,则来自&运算符的地址应在0​​x0000至0xFFFF之间。我们知道,&对于不同的执行,来自运算符的地址是不同的。我的问题是-导致内存地址分配不确定性的原因是什么?由于逻辑地址映射到物理地址,即使物理地址发生变化,也不应该具有一致的逻辑地址值吗?

另外,如果我分叉该进程,则子进程和父进程将为printf语句打印完全相同的输出。为什么在派生子进程时,即使它产生了新的进程,也不会发生上述行为?

消费税

引用@tpr在评论中链接答案,您观察到的地址差异是由于地址空间布局随机化

局部变量在堆栈上分配。传统上,堆栈分配是可重复的,但是最近几年发生了变化。地址空间布局随机化(ASR)是OS内存管理中的一项相对较新的创新,它故意使堆栈分配(例如您所观察到的)中的内存地址在运行时尽可能不确定。这是一项安全功能:防止不良参与者利用堆缓冲区溢出,因为如果ASLR实现足够熵,谁知道溢出缓冲区末尾会发生什么?

重要的是,ASLR适用于堆栈本身的分配(以及连接到可执行文件的其他数据区域)。简而言之,在Wikipedia上:

为了防止攻击者可靠地跳转到内存中的某个特定漏洞利用功能,ASLR随机排列进程的关键数据区域的地址空间位置,包括可执行文件的基础以及堆栈,堆的位置和图书馆。

在分叉的过程中,地址相同不是我最初回答的写时复制造成的即使您在派生过程中修改变量,地址也将保持不变(尽管将创建变量的副本)。尝试运行以下代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
  int a = 10;
  int status;
  printf("%d %p\n", a, &a);
  pid_t pid = fork();
  if (pid == 0)
  {
    printf("FORKED: %d %p\n", a, &a);
    a = 11;
    printf("FORKED: %d %p\n", a, &a);
    return 0;
  } else {
  wait(&status);
  printf("%d %p\n", a, &a);
  return 0;
  }
}

并且您将看到a仅在派生流程中对其进行了修改,但是父流程将对其进行打印而未更改。但是,所有打印行中的地址都相同。在编写此答案时,这有点令人惊讶,因此在搜索后我发现了这个问题答案很简单:

每个进程都有自己的4G虚拟地址空间,这是操作系统和硬件内存管理器将您的虚拟地址映射到物理地址的工作。

因此,尽管看起来两个进程的变量地址相同,但这只是虚拟地址。

内存管理器会将其映射到完全不同的物理地址

以下两个引号来自fork(2)手册页:

子进程是通过一个线程创建的,该线程称为fork()。父级的整个虚拟地址空间都在子级中复制,包括互斥体的状态,条件变量和其他pthreads对象

[...]

在Linux下,fork()是使用写时复制页面实现的,因此它唯一的代价就是复制父级的页表并为子级创建唯一的任务结构所需的时间和内存。

由于第二引号中提到的写时复制,对于分叉进程及其父进程中的相等虚拟内存地址,基本物理地址可能相同从维基百科:

写入时复制CoWCOW),有时也称为隐式共享阴影,是一种计算机管理中使用的资源管理技术,可有效地对可修改资源实施“复制”或“复制”操作。如果资源重复但未被修改,则无需创建新资源;资源可以在副本和原始副本之间共享。修改仍然必须创建一个副本,因此使用该技术:将复制操作推迟到第一次写入。

因此,直到变量被修改(或exec*称为家族成员)之前,相同的虚拟地址很可能对应于相同的物理地址(有关异常,请参见手册页)。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章