引用cmets中@tpr链接的answer,您观察到的地址差异是由于address space layout randomization:
局部变量在栈上分配。传统上,堆栈分配是可重复的,但近年来这种情况发生了变化。地址空间布局随机化 (ASR) 是 OS 内存管理中一项相对较新的创新,它故意使堆栈分配(例如您观察到的那些)中的内存地址在运行时尽可能不确定。这是一个安全功能:这可以防止不良行为者利用堆缓冲区溢出,因为如果 ASLR 实现足够熵,那么谁知道溢出缓冲区结束时会出现什么?
重要的是,ASLR 适用于堆栈本身的分配(连同连接到可执行文件的其他数据区域)。正如维基百科上的简明扼要:
为了防止攻击者可靠地跳转到例如内存中的特定被利用函数,ASLR 随机排列进程关键数据区域的地址空间位置,包括可执行文件的基址和堆栈、堆和库。
由于copy-on-write,正如我最初回答的那样,分叉进程中的地址相同是不。即使您在分叉的进程中修改变量,地址也将保持不变(尽管会制作变量的副本)。尝试运行以下代码:
#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 仅在分叉进程中被修改,但父进程将打印它不变。但是,地址在所有打印行中保持不变。写这个答案的时候有点意外,所以在搜索时我找到了this question。答案很简单:
每个进程都有自己的 4G 虚拟地址空间,操作系统和硬件内存管理器负责将虚拟地址映射到物理地址。
因此,虽然看起来两个进程具有相同的变量地址,但这只是虚拟地址。
内存管理器会将其映射到一个完全不同的物理地址
以下两句引自fork(2)手册页:
子进程是使用单个线程创建的——调用 fork() 的线程。父级的整个虚拟地址空间在子级中复制,包括互斥锁、条件变量和其他 pthread 对象的状态
[...]
在 Linux 下,fork() 是使用写时复制页实现的,因此它所招致的唯一损失是复制父页表以及为子页创建独特的任务结构所需的时间和内存。
由于第二个引用中提到的copy-on-write,底层物理地址可能与分叉进程及其父进程中相同的虚拟内存地址相同。来自维基百科:
Copy-on-write(CoW 或 COW),有时称为隐式 sharing 或 shadowing,是一种资源管理技术,用于计算机编程,以有效地实现对可修改资源的“复制”或“复制”操作。如果一个资源被复制但没有被修改,则不需要创建一个新的资源;资源可以在副本和原件之间共享。修改仍然必须创建副本,因此技术:复制操作推迟到第一次写入。
因此,在修改变量(或调用exec* 系列的成员)之前,相同的虚拟地址很可能对应于相同的物理地址(请参阅手册页了解例外情况)。