【问题标题】:Questions about address spaces in C++关于 C++ 中地址空间的问题
【发布时间】:2011-11-21 06:21:22
【问题描述】:

请阅读以下 C++ 代码和结果。根据一些维基页面,静态、自动和动态分配的变量被分配在不同的地址空间,即数据段、堆栈和堆。但是,在我看来,静态变量和动态变量的地址大致相同。为什么呢?我怎么知道静态变量真的在数据段中,而不是在堆中?

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

我还有一个问题是为什么 volatile 变量的地址是 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

【问题讨论】:

  • 每个进程都有一个地址空间。您将部分/段与地址空间混合在一起。
  • 那么我的问题就变成了:在 C++ 中我们能知道每个部分(或段)的范围(或大小)是多少吗?
  • 如果你的可修改全局变量接近分配给new的对象,那可能是因为加载器或程序自己的初始化代码(在调用main之前)已经为它们分配了一块内存,并且该块最终与 new 使用的来源相同。
  • 也试试 static const int ci; &ci 可能与 &i 非常不同。
  • @R。 Martinho Fernandes : 这实际上并不能以任何方式保证。在哈佛架构上,您至少有两个(函数指针的一个不同的)。这就是为什么你不能将void* 转换为void(*)() 的原因;甚至不能保证它们使用相同数量的位,即两个地址空间甚至不需要相同大小。

标签: c++


【解决方案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&lt;&lt;接受指向-volatile-T的指针,但是有一个接受bool的,任何指针都可以隐式转换为void*,然后可以转换为bool。要打印您想要的地址,将该行更改为

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

【讨论】:

    【解决方案2】:

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

    但它们可能位于不同的部分(或部分),具体取决于它们是静态的、本地的(自动的)还是动态的。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

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

        即使所有其他变量都相同,内存映射甚至会因同一程序的运行而不同。

        您可以搜索“win32 内存映射”和“linux 进程内存映射”以获取有关您的环境的更多详细信息。 (注意:这与“内存映射文件”不同。)

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

        对于 Linux,请咨询pmap command。对于 Windows,请尝试使用 sysinternals 工具之一。

        最后,您的 volatile 变量的地址实际上不是1,而是std::cout 以这种方式打印。有关更多信息,请参阅Why does std::cout convert volatile pointers to bool?

        【讨论】:

          【解决方案5】:

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

              int* k = new int[10];
          

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

          全局变量和具有文件作用域的变量(不在函数中的变量,例如“static int i”)位于数据部分(或者如果它没有定义任何值并且依赖于平台,则在所谓的 bss 中)。在某些平台上,代码部分中恰好有一小块数据。数据部分是文件的一部分。

          【讨论】:

            【解决方案6】:

            语言允许将变量放置在不同的内存区域这一事实并不需要任何特定的编译器/操作系统来真正使这些区域分开和不同(编译器确实必须遵循正确的语言规则,例如,new 分配的内存必须由delete 释放,尽管它​​可以在物理上接近以其他方式分配的内存)。比如malloc分配的内存(官方从堆中分配)和new分配的内存(官方从free store分配)之间的Standard makes a distinction;但我从未见过将它们放在不同的内存段中的实现。

            我还有一个问题是为什么 volatile 变量的地址是 1?

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

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

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

            【讨论】:

              【解决方案7】:

              如果您使用递归调用创建更大的堆栈,您可能会看到数据和堆栈段的更好分离。正如其他人所指出的,它们都属于同一个“地址空间”,但通常位于不同的社区。​​p>

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

              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
              

              【讨论】:

                猜你喜欢
                • 2011-05-26
                • 1970-01-01
                • 2012-07-17
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2022-06-18
                • 2014-02-18
                • 1970-01-01
                相关资源
                最近更新 更多