关于c++中地址空间的问题
Questions about address spaces in C++
请阅读下面的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
- C++/地址空间:每个地址 2 个字节?
- 任务管理器 - C/C++ 应用程序 - 分配的地址空间与已用内存
- size_t ptrdiff_t和地址空间
- C++中是否有一个函数可以为您获取指向该节点的所有指针的地址空间
- ptrA = ptrB 是否等同于 ptrA = &*ptrB?,空值是否共享一个地址空间?
- 优化大型地址空间上的循环
- 如何表示指向地址空间开头的指针
- 从某个特定但很大的虚拟地址空间(例如,20GB以上)分配内存
- Do C和C++标准意味着地址空间中必须只存在一个特殊值来表示空指针的值
- 使用mprotect保护整个地址空间
- 关于取消引用和地址空间的基本C++指针问题
- 进程如何访问不属于其虚拟地址空间的内存映射 IO 地址?
- 在虚拟地址空间中使用内存映射文件
- 由 shmat 返回的指针位于地址空间的末尾,这给出了 SEG 错误
- 与地址空间无关的对象表示
- 以何种形式,可以通过管道将空指针发送到不同进程地址空间中的另一个进程
- 窗口中的虚拟地址空间
- 在Clang中使用指向成员和地址空间的指针
- 更新进程地址空间中的HTML内容
- 关于c++中地址空间的问题