请阅读以下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
答案 0 :(得分:8)
只有操作系统可以告诉您哪些部分位于地址空间的哪些部分。如果您使用的是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<<
采用指针到易失性T,但有一个需要bool
,任何指针可以隐式转换为void*
,然后可以转换为bool
。要打印所需的地址,请将该行更改为
cout << const_cast<int*>(&l) << endl;
答案 1 :(得分:4)
根据定义,您的变量都将位于相同的地址空间中。
但它们可能属于不同的部分(或部分),具体取决于它们是静态的,本地的(自动的)还是动态的。
答案 2 :(得分:4)
这完全取决于平台,但这是我理解的方式:有三个相关的段,文本,数据和堆栈。文本段包含代码。数据段包含静态变量。堆栈段包含堆和堆栈,它们从相对的两端填充堆栈段:
| Text | Data | ---> Heap Stack <--- |
i k j
由于数据大小在编译时是已知的,我想堆栈段将紧随其后,因此第一个堆分配应该在最后一个静态变量之后。另一方面,第一个堆栈分配尽可能远,由堆栈段的大小决定。
答案 3 :(得分:3)
您询问的是流程的内存映射,或内存段的顺序和位置。这在不同的执行环境(WIN32内存映射必然不同于Linux内存映射),不同版本之间(XP内存映射可能与Windows7内存映射不同)和不同的CPU模式(显然是x86和x86-)之间有所不同64不同)。
即使所有其他变量相同,内存映射甚至可能因同一程序的运行而不同。
您可以google“win32内存映射”和“linux进程内存映射”以获取有关您的环境的更多详细信息。 (注意:这与“内存映射文件”不同。)
不可能(以任何可移植的方式)确定各种存储器段的范围和大小(甚至数量或存在)。例如,C ++没有要求静态数据地址没有穿插动态数据地址。
对于Linux,请参阅pmap command。对于Windows,请尝试使用sysinternals工具之一。
最后,您的volatile变量的地址实际上不是1
,而是std::cout
正在以这种方式打印它。有关详细信息,请参阅Why does std::cout convert volatile pointers to bool?。
答案 4 :(得分:1)
语言允许将变量放在不同的内存区域这一事实不会要求任何特定的编译器/操作系统实际上使这些区域分离且不同(编译器必须遵循正确的语言规则,例如new
分配的内存必须由delete
解除分配,尽管它可以在物理上接近以其他方式分配的内存)。例如,malloc
分配的内存(从堆正式分配)和new
分配的内存之间的Standard makes a distinction(从免费商店正式分配);但是我从未见过一个实现将它们放在不同的内存段中。
我的另一个问题是为什么volatile变量的地址是1?
变量的地址不是1.但是,std::iostream
(例如,std::cout
)没有volatile
指针的重载,最接近的合法重载是{{ 1}}。因此,输出bool
指针的地址将输出volatile
(如果指针为0
)或NULL
(所有其他情况)。
更广泛的问题是,在C ++中是否有可能知道每个地址空间的范围(或可用大小)?
在特定平台上可能有可能,但没有任何跨平台的方法可以做到这一点。
答案 5 :(得分:1)
在函数中分配的局部变量占用堆栈空间(否则递归函数不起作用)。但
int* k = new int[10];
会在某个时候调用malloc,因为'new'会从全局存储中分配。所以* i在您的代码中虽然是局部变量,但指向全局空间。
具有文件范围的全局变量和变量(不在函数中的变量 - 如'static int i')在数据部分中(或者如果它没有定义任何值并且它依赖于平台,则在所谓的bss中)。在某些平台上,会发生一小部分数据存在于代码段中。数据部分是文件的一部分。
答案 6 :(得分:0)
如果使用递归调用创建更大的堆栈,您可能会看到更好的数据和堆栈段分离。正如其他人所指出的那样,它们都属于同一个“地址空间”,但通常位于不同的社区。 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