我一直觉得内存是很复杂的东西......也许我把这篇文章完成的时候,我会了解一点c++的内存管理机制
从硬件开始
内存器地址空间->总线地址空间->cpu地址空间->虚拟内存地址空间->程序地址空间(逻辑地址空间)
程序地址空间对c++程序员来说是可见的,其他地址空间我们并不关心
通过打印pointer的值 可以得到我们设立的变量和代码段在程序地址空间得分布情况
虚拟内存地址空间由操作系统管理,它把复杂的内存地址空间虚拟成一个平坦的 巨大的虚拟内存地址空间
在32位机上,这一空间大小是4GB
在c++程序中 内存空间被分为几个区域:
常量 栈 堆 自由存储 全局和静态
int main()
{
char * p1 = " hello " ;
char p2[ 10 ] = " hello " ;
char * p3 = " hello " ;
printf( " %d %d %d " ,p1,p2,p3);
getchar();
}
从这段代码可以看到 p1和p3地址相同(这不是一定的,取决于编译器优化,但现在的编译器几乎都是)它们跟p2相隔很远 其原因就是 字符串常量保存于常量区,常量区的值不能被修改。
但是const修饰符跟常量区无关。
exceptional c++还特别强调了一点:堆跟自由存储之间的关系。标准中堆是malloc realloc和free这样的c函数使用的内存 自由存储是new和delete使用的内存 标准没有规定他们是否相关联 唯一可以肯定的是 malloc 和free不会用new 和delete实现。
堆跟自由存储这样有些暧昧的联系确实令人难以理解,在我的认识中 new 和delete的语义更倾向于说明性 malloc和free更倾向于过程性(命令性),即new和delete只关心结果 malloc和free则明确规定了实现方式。相应地,自由存储和堆的语义也有类似的关系,一般来说 自由存储由堆来实现是很自然的。
栈是一个跟汇编语言中栈几乎重叠的概念,在c++中 栈负责函数参数传递和函数局部变量存储(包括main),所有函数共享一个栈,他们各自使用一个栈段。像在汇编中一样 栈内存不仅可以由push和pop操作,还能被随机访问。(除了栈顶指针寄存器之外,还有栈偏移指针寄存器)因此栈内存中的变量可以随意使用。
全局和静态变量空间 凡是static修饰的变量和函数都存储在这一空间中 全局变量也存储在这个空间中,其中还包括了全局函数和类的静态成员函数。
特别提出的是 类的非静态成员函数不在其中,类的非静态成员函数不允许取地址,也无法通过函数指针访问。甚至类型强制转换都被不允许。换句话说,类的成员函数就好像不在内存中一样。
这幅图片盗链自msdn :P 它属于http://www.microsoft.com/china/msdn/archives/library/techart/heap3.asp#heap3_commonproblems一文

从这里看出一个c++程序的alloc函数显然不会理会win32api中的内存分配函数
来自那篇文章的解释:C/C++ 运行时 (CRT) 分配程序创建自己的私有堆,驻留在 Win32 堆的顶部
在所有虚拟内存系统中,堆驻留在操作系统的“虚拟内存管理器”的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。
写到这里我似乎发现话题离c++越来越远了 现在我不想关心c++是如何工作在虚拟内存上的,回到更贴近c++编程的问题上来,类在内存中如何摆放。
我写了一段程序来观察c++的类的内存:
{
cout << " " << " address: " <<* ( int * ) & var << endl;
cout << " " << " size: " << sizeof ( * var) << endl;
cout << " " << " bin value: " ;
char * start = ( char * )var;
for ( int i = 0 ;i < sizeof ( * var);i ++ )
{
char k = start[i];
int c = 8 ;
while (c -- )
{
cout << k % 2 ;
k /= 2 ;
}
cout << " " ;
}
cout << endl;
}
其实我是这样想的 它接受2个参数 编译时用模板接受一个类型 运行时接受一个指针 然后根据它们来查看一个对象的内存使用情况。
我把它用于这样一段代码
#include
<
iostream
>
using
namespace
std;
class
cls
...
{
public:
int a;
char b;
char c;
cls():a(5),b(97),c(2)
...{
}
}
;
template
<
class
T
>
void
memory_show(T
*
var);
int
main()
...
{
cls a;
cout<<"cls object a:"<<endl;
memory_show<cls>(&a);
cout<<"int a.a:"<<endl;
memory_show<int>(&a.a);
cout<<"char a.b:"<<endl;
memory_show<char>(&a.b);
cout<<"char a.c:"<<endl;
memory_show<char>(&a.c);
getchar();
}
cls object a:
address:2293616
size:8
bin value:10100000 00000000 00000000 00000000 10000110 01000000 00000000 0000
0000
int a.a:
address:2293616
size:4
bin value:10100000 00000000 00000000 00000000
char a.b:
address:2293620
size:1
bin value:10000110
char a.c:
address:2293621
size:1
bin value:01000000
从这个结果分析
1.类的地址是连续分配的 不会对齐
2.类的大小是32位字长的整数倍 也就是说 总是4的倍数
3.普通成员函数不占空间
前面说过,几乎无法得到类的成员函数的指针(无法获得地址 也就无法获得指针)这样就没办法观察它们在内存中的分布了 但是 虚函数是个例外 也许我们可以从虚函数入手 观察类的成员函数。
在cls中定义一个虚函数 cls变成了
对象a的长度变成了12字节
class
cls
...
{
public:
int a;
char b;
char c;
virtual void f()...{};
cls():a(5),b(97),c(2)
...{
}
}
;
对象a的内存变成了下面的
00000001 00111000 00100010 00000000 10100000 00000000 00000000 00000000 10000110 01000000 00000001 00111110
红色部分是添加上去的虚函数 c++把虚函数实现为一个指针 它指向了要调用的子类或者自身的成员函数
所有同一类对象的虚函数指针是相同的 如果父类中没有虚函数的实现 那么虚函数指针被设为0(null) 它成为一个纯虚函数
有趣的是 在添加虚函数之前 类的未使用内存(就是不够4的倍数被补上去的那几字节) 是整齐的 添加之后 它们被赋予特定的值(不知道是不是有效的)
进一步测试 添加一个或几个虚函数不会造成类的占用空间增大或者改变
利用虚函数指针的这一特点 可以研究类的成员函数在内存中的分布情况
to be continued......
本文探讨了C++的内存管理,从硬件层面到程序地址空间,详细讲解了内存的划分,包括常量、栈、堆、自由存储和全局静态。通过实例展示了类在内存中的布局,以及虚函数如何影响内存分配。并提到了C++运行时如何管理内存,以及内存分配与Win32API的关系。

被折叠的 条评论
为什么被折叠?



