关于c++中地址空间的问题

Questions about address spaces in C++

本文关键字:问题 地址空间 c++ 关于      更新时间:2023-10-16

请阅读下面的c++代码和结果。根据一些wiki页面,静态、自动和动态分配的变量分配在不同的地址空间中,即数据段、堆栈和堆。然而,在我看来,静态和动态变量的地址是在大约相同的地方。为什么会这样呢?我怎么知道静态变量是在数据段中,而不是在堆中?

一个更广泛的问题是,是否有可能在c++中知道每个地址空间的范围(或可用大小)?

我的另一个问题是为什么易失性变量的地址是1?

#include <iostream>
using namespace std;
static int i;
int main() {
    cout << sizeof(int*) << endl;
    int j;
    int* k = new int[10];
    volatile int l;
    cout << &i << endl;
    cout << &j << endl;
    cout << k << endl;
    cout << &l << endl;
    delete[] k;
}

结果:

8
0x1000010e4
0x7fff5fbff66c
0x100100080
1

只有操作系统才能告诉您哪个部分位于地址空间的哪个部分。如果您使用的是Linux,从程序中输出/proc/self/maps的内容:

我会增加

std::ifstream maps("/proc/self/maps");
std::cout << maps.rdbuf();

在你的程序末尾,它打印:

8
0x6021c0
0x7fffe07f60bc
0x603010
1
...
00601000-00602000 r--p 00001000 09:01 9175691   /home/cubbi/test
                  ^ -- read-only static data
00602000-00603000 rw-p 00002000 09:01 9175691  /home/cubbi/test
                  ^^ -- writeable static data
00603000-00624000 rw-p 00000000 00:00 0         [heap]
...
7fffe07d7000-7fffe07f9000 rw-p 00000000 00:00 0  [stack]

对于volatile int的地址打印,没有标准的operator<<指针指向volatile- t,但有标准的bool指针指向volatile- t,并且任何指针都可以隐式转换为void*,然后再转换为bool。要打印所需的地址,请将该行更改为

cout << const_cast<int*>(&l) << endl;

根据定义,您的变量将全部位于相同的地址空间。

但是它们可能在不同的节(或段)中,这取决于它们是静态的,本地的(自动的)还是动态的。

这是非常依赖于平台的,但我是这样理解的:有三个相关的部分,文本,数据和堆栈。文本段包含代码。数据段包含静态变量。堆栈段包含堆和堆栈,它们从相反的两端填充堆栈段:

| Text | Data | ---> Heap        Stack <--- |
            i   k                         j

由于数据大小在编译时是已知的,我认为堆栈段将紧随其后,因此第一个堆分配应该在最后一个静态变量之后。另一方面,第一个堆栈分配是尽可能远的,由堆栈段的大小决定。

您正在询问进程的内存映射,或者内存段的顺序和位置。这在不同的执行环境(WIN32内存映射必然不同于Linux内存映射),不同版本(XP内存映射可能不同于Windows7内存映射)和不同的CPU模式(显然x86和x86-64不同)之间是不同的。

即使在所有其他变量相等的情况下,同一程序的内存映射甚至可以在每次运行时不同。

你可以谷歌"win32内存映射"answers"linux进程内存映射"来了解更多关于你的环境的细节。(注意:这与"内存映射文件"不同)

不可能(以任何可移植的方式)确定各种内存段的范围和大小(甚至是数量或存在)。例如,c++没有要求静态数据地址不与动态数据地址穿插在一起。

对于Linux,请参考pmap命令。对于Windows,请尝试其中一个sysinternals工具。

最后,您的易失性变量的地址实际上不是1,而是std::cout以这种方式打印它。参见为什么std::cout将volatile指针转换为bool类型?了解更多相关信息。

在函数中分配的局部变量占用堆栈空间(否则递归函数将无法工作)。但

    int* k = new int[10];

将在某个时刻调用malloc,因为'new'从全局存储中分配。所以*i在你的代码虽然是局部变量,指向全局空间。

全局变量和具有文件作用域的变量(不属于函数的变量,比如'static int i')在data section中(或者在所谓的bss中,如果它没有定义任何值并且依赖于平台)。在某些平台上,代码段中会出现小块数据。数据段是文件的一部分。

语言允许变量放置在不同的内存区域的事实并不要求任何特定的编译器/操作系统实际上使这些区域分开和不同(编译器必须遵循正确的语言规则,例如,new分配的内存必须由delete释放,尽管它可以在物理上接近其他方式分配的内存)。例如,标准区分了malloc分配的内存(从堆中正式分配)和new分配的内存(从自由存储中正式分配);但是我从来没有见过一个实现把它们放在不同的内存段。

我的另一个问题是为什么易失性变量的地址是1?

变量的地址不是1。然而,std::iostream(例如,std::cout)对volatile指针没有重载,最接近的合法重载是bool。因此,输出volatile指针的地址将输出0(如果指针是NULL)或1(所有其他情况)。

一个更广泛的问题是,是否有可能在c++中知道每个地址空间的范围(或可用大小)?

这在特定的平台上可能是可行的,但是没有任何跨平台的方法可以做到这一点。

如果使用递归调用创建更大的堆栈,则可以更好地分离数据段和堆栈段。正如其他人所指出的,他们都是同一个"地址空间"的一部分,但通常在不同的社区。

#include <iostream>
using namespace std;
int print_addresses(int depth) {
    int j;
    int* k = new int[10];
    volatile int l;
    static int i;
    cout << "&i = " << &i << " "
     << "&j = " << &j << " "
     << "k = " << k << " "
     << "&l = " << (int *)&l 
     << endl;

    if (depth < 10)
      print_addresses(depth + 1);
    delete[] k;
}

int main() {
  cout << sizeof(int*) << endl;
  print_addresses(0);
}

在x86-64 linux机器上,我得到以下输出。它表明静态变量具有相同的地址。当我进行更多的函数调用时,堆栈变量的地址会增加,但是堆分配变量的地址会减少。此行为是特定于编译器和平台的,因此不应该依赖此行为。可以通过将volatile的指针转换为int指针来获得它的地址。

sizeof(int*)=8
&i = 0x6011a4 &j = 0x7fff9f67bb8c k = 0x15e7010 &l = 0x7fff9f67bb88
&i = 0x6011a4 &j = 0x7fff9f67bb5c k = 0x15e7040 &l = 0x7fff9f67bb58
&i = 0x6011a4 &j = 0x7fff9f67bb2c k = 0x15e7070 &l = 0x7fff9f67bb28
&i = 0x6011a4 &j = 0x7fff9f67bafc k = 0x15e70a0 &l = 0x7fff9f67baf8
&i = 0x6011a4 &j = 0x7fff9f67bacc k = 0x15e70d0 &l = 0x7fff9f67bac8
&i = 0x6011a4 &j = 0x7fff9f67ba9c k = 0x15e7100 &l = 0x7fff9f67ba98
&i = 0x6011a4 &j = 0x7fff9f67ba6c k = 0x15e7130 &l = 0x7fff9f67ba68
&i = 0x6011a4 &j = 0x7fff9f67ba3c k = 0x15e7160 &l = 0x7fff9f67ba38
&i = 0x6011a4 &j = 0x7fff9f67ba0c k = 0x15e7190 &l = 0x7fff9f67ba08
&i = 0x6011a4 &j = 0x7fff9f67b9dc k = 0x15e71c0 &l = 0x7fff9f67b9d8
&i = 0x6011a4 &j = 0x7fff9f67b9ac k = 0x15e71f0 &l = 0x7fff9f67b9a8