C/C++措施的内存分派方法
副标题#e#
1.内存分派方法
内存分派方法有三种:
[1]从静态存储区域分派。内存在措施编译的时候就已经分派好,这块内存在措施的整个 运行期间都存在。譬喻全局变量,static变量。
[2]在栈上建设。在执行函数时,函数内局部变量的存储单位都可以在栈上建设,函数执 行竣事时这些存储单位自动被释放。栈内存分派运算内置于处理惩罚器的指令会合,效率很高, 可是分派的内存容量有限。
[3]从堆上分派,亦称动态内存分派。措施在运行的时候用malloc或new申请任意几多的内 存,措施员本身认真在何时用free或delete释放内存。动态内存的保留期由措施员抉择,使 用很是机动,但假如在堆上分派了空间,就有责任接纳它,不然运行的措施会呈现内存泄漏 ,频繁地分派和释放差异巨细的堆空间将会发生堆内碎块。
2.措施的内存空间
一个措施将操纵系统分派给其运行的内存块分为4个区域,如下图所示。
代码区(code area) | 措施内存空间 |
全局数据区(data area) | |
堆区(heap area) | |
栈区(stack area) |
一个由C/C++编译的措施占用的内存分为以下几个部门,
1、栈区(stack)— 由编译器自动分派释放 ,存放为运行函数而分派的局部变 量、函数参数、返回数据、返回地点等。其操纵方法雷同于数据布局中的栈。
2、堆区(heap) — 一般由措施员分派释放, 若措施员不释放,措施竣事时可 能由OS接纳 。分派方法雷同于链表。
3、全局区(静态区)(static)—存放全局变量、静态数据、常量。措施竣事后由 系统释放。
4、文字常量区 —常量字符串就是放在这里的。 措施竣事后由系统释放。
5、措施代码区—存放函数体(类成员函数和全局函数)的二进制代码。
下面给出例子措施,
int a = 0; //全局初始化区
char *p1; //全局未初始化区
int main() {
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456在常量区,p3在栈上。
static int c =0;//全局(静态)初始化区
p1 = new char[10];
p2 = new char[20];
//分派得来得和字节的区域就在堆区。
strcpy(p1, "123456"); //123456放在常量区,编译器大概会将它与p3所指向的 "123456"优化成一个处所。
}
#p#副标题#e#
3.堆与栈的较量
3.1申请方法
stack: 由系统自动分派。 譬喻,声明在函数中一个局部变量 int b; 系统自动在栈中为 b开发空间。
heap: 需要措施员本身申请,并指明巨细,在C中malloc函数,C++中是new运算符。
如p1 = (char *)malloc(10); p1 = new char[10];
如p2 = (char *)malloc(10); p2 = new char[20];
可是留意p1、p2自己是在栈中的。
3.2申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为措施提供内存,不然将报异常提示栈溢 出。
堆:首先应该知道操纵系统有一个记录空闲内存地点的链表,当系统收到措施的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中 删除,并将该结点的空间分派给措施。
对付大大都系统,会在这块内存空间中的首地点处记录本次分派的巨细,这样,代码中的 delete语句才气正确的释放本内存空间。
由于找到的堆结点的巨细不必然正好便是申请的巨细,系统会自动的将多余的那部门从头 放入空闲链表中。
3.3申请巨细的限制
栈:在Windows下,栈是向低地点扩展的数据布局,是一块持续的内存的区域。这句话的意 思是栈顶的地点和栈的最大容量是系统预先划定好的,在 WINDOWS下,栈的巨细是2M(也有 的说是1M,总之是一个编译时就确定的常数),假如申请的空间高出栈的剩余空间时,将提 示overflow。因 此,能从栈得到的空间较小。
堆:堆是向高地点扩展的数据布局,是不持续的内存区域。这是由于系统是用链表来存储 的空闲内存地点的,自然是不持续的,而链表的遍历偏向是由低地点向高地点。堆的巨细受 限于计较机系统中有效的虚拟内存。由此可见,堆得到的空间较量机动,也较量大。
3.4申请效率的较量
栈由系统自动分派,速度较快。但措施员是无法节制的。
堆是由new分派的内存,一般速度较量慢,并且容易发生内存碎片,不外用起来最利便。
#p#分页标题#e#
别的,在WINDOWS下,最好的方法是用VirtualAlloc分派内存,他不是在堆,也不是栈, 而是直接在历程的地点空间中保存一快内存,固然用起来最不利便。可是速度快,也最机动 。
3.5堆和栈中的存储内容
栈:在函数挪用时,第一个进栈的是主函数中后的下一条指令(函数挪用语句的下一条可 执行语句)的地点,然后是函数的各个参数,在大大都的C编译器中,参数是由右往左入栈的 ,然后是函数中的局部变量。留意静态变量是不入栈的。
当本次函数挪用竣事后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地 址,也就是主函数中的下一条指令,措施由该点继承运行。
堆:一般是在堆的头部用一个字节存放堆的巨细。堆中的详细内容有措施员布置。
3.6存取效率的较量
char s1[] = "a";
char *s2 = "b";
a是在运行时刻赋值的;而b是在编译时就确定的;可是,在今后的存取中,在栈上的数组 比指针所指向的字符串(譬喻堆)快。 好比:
int main(){
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return 0;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到 edx中,再按照edx读取字符,显然慢了。
3.7小结
堆和栈的主要区别由以下几点:
1、打点方法差异;
2、空间巨细差异;
3、可否发生碎片差异;
4、发展偏向差异;
5、分派方法差异;
6、分派效率差异;
打点方法:对付栈来讲,是由编译器自动打点,无需我们手工节制;对付堆来说,释放工 作由措施员节制,容易发生memory leak。
空间巨细:一般来讲在32位系统下,堆内存可以到达4G的空间,从这个角度来看堆内存几 乎是没有什么限制的。可是对付栈来讲,一般都是有必然的空间巨细的,譬喻,在VC6下面, 默认的栈空间巨细是1M。虽然,这个值可以修改。
碎片问题:对付堆来讲,频繁的new/delete势必会造成内存空间的不持续,从而造成大量 的碎片,使措施效率低落。对付栈来讲,则不会存在这个问题,因为栈是先进后出的行列, 他们是如此的一一对应,以至于永远都不行能有一个内存块从栈中间弹出,在他弹出之前, 在他上面的后进的栈内容已经被弹出,具体的可以参考数据布局。
发展偏向:对付堆来讲,发展偏向是向上的,也就是向着内存地点增加的偏向;对付栈来 讲,它的发展偏向是向下的,是向着内存地点减小的偏向增长。
分派方法:堆都是动态分派的,没有静态分派的堆。栈有2种分派方法:静态分派和动态 分派。静态分派是编译器完成的,好比局部变量的分派。动态分派由malloca函数举办分派, 可是栈的动态分派和堆是差异的,他的动态分派是由编译器举办释放,无需我们手工实现。
分派效率:栈是呆板系统提供的数据布局,计较时机在底层对栈提供支持:分派专门的寄 存器存放栈的地点,压栈出栈都有专门的指令执行,这就抉择了栈的效率较量高。堆则是 C/C++函数库提供的,它的机制是很巨大的,譬喻为了分派一块内存,库函数会凭据必然的算 法(详细的算法可以参考数据布局/操纵系统)在堆内存中搜索可用的足够巨细的空间,假如 没有足够巨细的空间(大概是由于内存碎片太多),就有大概挪用系统成果去增加措施数据 段的内存空间,这样就有时机分 到足够巨细的内存,然后举办返回。显然,堆的效率比栈要 低得多。
从这里我们可以看到,堆和栈对比,由于大量new/delete的利用,容易造成大量的内存碎 片;由于没有专门的系统支持,效率很低;由于大概激发用户态和焦点态的切换,内存的申 请,价钱变得越发昂贵。所以栈在措施中是应用最遍及的,就算是函数的挪用也操作栈去完 成,函数挪用进程中的参数,返回地点, EBP和局部变量都回收栈的方法存放。所以,我们 推荐各人只管用栈,而不是用堆。
固然栈有如此浩瀚的长处,可是由于和堆对比不是那么机动,有时候分派大量的内存空间 ,照旧用堆好一些。
#p#分页标题#e#
无论是堆照旧栈,都要防备越界现象的产生(除非你是存心使其越界),因为越界的功效 要么是措施瓦解,要么是摧毁措施的堆、栈布局,发生以想不到的功效。
4.new/delete与malloc/free较量
从C++角度上说,利用new分派堆空间可以挪用类的结构函数,而malloc()函数仅仅是一个 函数挪用,它不会挪用结构函数,它所接管的参数是一个unsigned long范例。同样,delete 在释放堆空间之前会挪用析构函数,而free函数则不会。
class Time{
public:
Time(int,int,int,string);
~Time(){
cout<<"call Time's destructor by:"<<name<<endl;
}
private:
int hour;
int min;
int sec;
string name;
};
Time::Time(int h,int m,int s,string n){
hour=h;
min=m;
sec=s;
name=n;
cout<<"call Time's constructor by:"<<name<<endl;
}
int main(){
Time *t1;
t1=(Time*)malloc(sizeof(Time));
free(t1);
Time *t2;
t2=new Time(0,0,0,"t2");
delete t2;
system("PAUSE");
return EXIT_SUCCESS;
}
功效:
call Time’s constructor by:t2
call Time’s destructor by:t2
从功效可以看出,利用new/delete可以挪用工具的结构函数与析构函数,而且示例中挪用 的是一个非默认结构函数。但在堆上分派工具数组时,只能挪用默认结构函数,不能挪用其 他任何结构函数。