动态内存分布
C语言中的动态分配:
malloc与free函数
内存区域
全局变量,静态数据,常量 | data area |
所有类成员函数和非成员函数代码 | code area |
为运行函数分配的局部变量,函数参数,返回数据,返回地址等 | stack area(栈) |
动态内存分配区 | heap area (堆) |
C++中的内存分配:
C++的运算符new 与delete
在堆上生成对象时,需要自动调用构造函数;
在堆上生成的对象,在释放时需要自动调用析构函数
new与delete,malloc与free需要配对使用;
new[]和delete[]生成和释放对象数组;//必须配对使用
new和delete是运算符,malloc与free是函数调用; new与delete可以重载,malloc与free不能进行重载
看看简单的测试程序:
14 #include<iostream>
15 using namespace std;
16
17 class Test{
18 public:
19 Test():m_val(0)
20 {
21 cout<<"Test"<<endl;
22 }
23 ~Test(){
24 cout<<"~Test"<<endl;
25 }
26 private:
27 int m_val;
28 };
29 int main()
30 {
31 {
32 Test a; //a的生命周期仅在于两个大括号之间,a在栈区
33 }
34 cout<<"end of }"<<endl;
35
36 Test *pVal = new Test(); //使用运算符new,直接调用类的构造函数
37 delete pVal; //调用析构函数
38 pVal=NULL;//编程规范
39 return 0;
40 }
编译运行结果:
[hongfuhao@hongfuhao main]$ g++ main.cpp
[hongfuhao@hongfuhao main]$ ./a.out
Test
~Test
end of }
Test
~Test
[hongfuhao@hongfuhao main]$
new[]与delete[] 与new,delete不同:添加两行代码与之前进行比较
14 #include<iostream>
15 using namespace std;
16
17 class Test{
18 public:
19 Test():m_val(0)
20 {
21 cout<<"Test"<<endl;
22 }
23 ~Test(){
24 cout<<"~Test"<<endl;
25 }
26 private:
27 int m_val;
28 };
29 int main()
30 {
31 {
32 Test a;
33 }
34 cout<<"end of }"<<endl;
35
36 Test *pVal = new Test();
37 delete pVal;
38 pVal=NULL;
39
40 Test *pArray=new Test[2]; //调用两次构造函数
41
42 delete [] pArray; //调用两次析构函数
43
44 return 0;
45 }
编译运行结果:
[hongfuhao@hongfuhao main]$ g++ main.cpp
[hongfuhao@hongfuhao main]$ ./a.out
Test
~Test
end of }
Test
~Test
Test
Test
~Test
~Test
[hongfuhao@hongfuhao main]$
上述代码如果单独调用delete,将42行代码改成 delete pArray;
[hongfuhao@hongfuhao main]$ g++ main.cpp
[hongfuhao@hongfuhao main]$ ./a.out
Test
~Test
end of }
Test
~Test
Test
Test
~Test
*** glibc detected *** ./a.out: munmap_chunk(): invalid pointer: 0x0000000000c6a018 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3785675f4e]
./a.out[0x400afe]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x378561ed5d]
./a.out[0x4008e9]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:02 403873 /home/hongfuhao/main/a.out
00601000-00602000 rw-p 00001000 08:02 403873 /home/hongfuhao/main/a.out
00c6a000-00c8b000 rw-p 00000000 00:00 0 [heap]
3784e00000-3784e20000 r-xp 00000000 08:02 1704388 /lib64/ld-2.12.so
378501f000-3785020000 r--p 0001f000 08:02 1704388 /lib64/ld-2.12.so
3785020000-3785021000 rw-p 00020000 08:02 1704388 /lib64/ld-2.12.so
3785021000-3785022000 rw-p 00000000 00:00 0
3785200000-3785283000 r-xp 00000000 08:02 1704402 /lib64/libm-2.12.so
3785283000-3785482000 ---p 00083000 08:02 1704402 /lib64/libm-2.12.so
3785482000-3785483000 r--p 00082000 08:02 1704402 /lib64/libm-2.12.so
3785483000-3785484000 rw-p 00083000 08:02 1704402 /lib64/libm-2.12.so
3785600000-378578a000 r-xp 00000000 08:02 1704389 /lib64/libc-2.12.so
378578a000-378598a000 ---p 0018a000 08:02 1704389 /lib64/libc-2.12.so
378598a000-378598e000 r--p 0018a000 08:02 1704389 /lib64/libc-2.12.so
378598e000-378598f000 rw-p 0018e000 08:02 1704389 /lib64/libc-2.12.so
378598f000-3785994000 rw-p 00000000 00:00 0
378f200000-378f216000 r-xp 00000000 08:02 1704368 /lib64/libgcc_s-4.4.7-20120601.so.1
378f216000-378f415000 ---p 00016000 08:02 1704368 /lib64/libgcc_s-4.4.7-20120601.so.1
378f415000-378f416000 rw-p 00015000 08:02 1704368 /lib64/libgcc_s-4.4.7-20120601.so.1
378fe00000-378fee8000 r-xp 00000000 08:02 1977349 /usr/lib64/libstdc++.so.6.0.13
378fee8000-37900e8000 ---p 000e8000 08:02 1977349 /usr/lib64/libstdc++.so.6.0.13
37900e8000-37900ef000 r--p 000e8000 08:02 1977349 /usr/lib64/libstdc++.so.6.0.13
37900ef000-37900f1000 rw-p 000ef000 08:02 1977349 /usr/lib64/libstdc++.so.6.0.13
37900f1000-3790106000 rw-p 00000000 00:00 0
7f53fffae000-7f53fffb3000 rw-p 00000000 00:00 0
7f53fffc8000-7f53fffcb000 rw-p 00000000 00:00 0
7ffdceb3f000-7ffdceb54000 rw-p 00000000 00:00 0 [stack]
7ffdcebee000-7ffdcebef000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
已放弃 (core dumped)
[hongfuhao@hongfuhao main]$
以上结果是C++中典型的一个出错结果;
只调用一次析构函数,然后崩溃,此时会造成内存泄漏
内存区域
代码存放在代码区,数据则根据类型的不同存放在不同区域中
Bss段:未初始化或者初始化为0的全局变量;
实例代码:bss.c
14 int bss_array[1024*1024];
15
16 int main(int argc,char * argv[])
17 {
18 return 0;
19 }
~
编译运行:
[hongfuhao@hongfuhao main]$ gcc bss.c -o bss
[hongfuhao@hongfuhao main]$ file bss
bss: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
[hongfuhao@hongfuhao main]$ ls -alh bss
-rwxrwxr-x. 1 hongfuhao hongfuhao 6.2K 11月 16 01:21 bss
[hongfuhao@hongfuhao main]$ strip bss //strip使文件代码变得更小
[hongfuhao@hongfuhao main]$ ls -alh bss
-rwxrwxr-x. 1 hongfuhao hongfuhao 4.1K 11月 16 01:22 bss
在程序运行周期内,bss数据一直都存在;//变程时尽量不使用全局变量
Data段存放初始化为非零的全局变量;
实例:
14 int bss_array[1024*1024]={1};
15
16 int main(int argc,char * argv[])
17 {
18 return 0;
19 }
~
文件大小:
[hongfuhao@hongfuhao main]$ gcc -o data bss.c
[hongfuhao@hongfuhao main]$ file data
data: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, not stripped
[hongfuhao@hongfuhao main]$ ls -alh data
-rwxrwxr-x. 1 hongfuhao hongfuhao 4.1M 11月 16 01:30 data
[hongfuhao@hongfuhao main]$
文件大小变得很大
在程序整个运行周期内,data数据一直存在;
静态变量在第一次进入作用域时被初始化,以后不必再初始化;
静态成员变量在类之间共享数据,也是放在全局/静态数据区中。并且只有一份拷贝。
在上篇博客,火星人游戏中有详细介绍:http://blog.csdn.net/hongfuhaocomon/article/details/53134963
常量数据区rodata区
常量不一定放在rodata中,有些立即数直接和指令编码在一起,放在text(代码区)中。
字符串常量,编译器会去掉重复的字符串,保证只有一个副本。
常量是不能修改的
字符串会被编译器自动放到rodata中,加上关键字const修饰的全局变量也放在rodata中。
典型例子代码:
13 #include <stdio.h>
14 #include <stdlib.h>
15 int main()
16 {
17 char *p = "hello";
18 char *p2 = "hello";
19 if (p==p2 )
20 { printf("Smatr complier, set the string in one place!\n");
21 }
22
23 char *p3 = (char *)malloc(12);
24 memset(p3,0,12);
25 strcpy(p3,p2);
26 printf("p3 = %s\n",p3);
27 p3[1]= 'x';
28 printf("p3 = %s\n",p3);
29 free(p3);
30 p3 = NULL;
31 p[1]='x';
32 return 0;
33 }
编译运行:
[hongfuhao@hongfuhao ~]$ gcc -o hello.c hello
[hongfuhao@hongfuhao ~]$ ./hello
Smatr complier, set the string in one place!
p3 = hello
p3 = hxllo
段错误 (core dumped)
[hongfuhao@hongfuhao ~]$
出现段错误,即常量不可修改;
栈中存储自动变量或者局部变量,以及传递的参数等;
在一个函数内部定义一个变量,或者向函数传递参数时,这些变量和参数存储在栈上,当变量推出这些变量的作用域时,这些栈上的存储单元会被自动释放;
堆是用户程序控制的存储区,存储动态产生的数据
当用mallic / new 来申请一块内存区域或者创建一个对象时,申请的内存在堆上分配,需要记录得到的地址,并且在不需要的时候释放这些内存。
栈一般很小,满足不了程序的逻辑的要求;
对象的生命周期是指对象从创建到被销毁的过程,创建对象时要占用一定的内存。因此在整个程序占用的内存随着对象的创建与销毁动态的发生变化。
变量的作用域决定了对象的生命周期。
全局对象在man函数之前被创建,main退出后被销毁
静态对象和全局对象类似,第一次进入作用域时被创建,但是程序开始时,内存已经分配好;
例子:
13 #include <stdio.h>
14 #include <stdlib.h>
15
16 class A {
17 public:
18 A(){printf("A created\n");}
19 ~A(){printf("A destroyed\n");}
20
21 };
22
23 class B{
24 public:
25 B(){printf("B created\n");}
26 ~B(){printf("B destroyed\n");}
27 };
28
29 A globalA;
30
31 B globalB;
32 int foo()
33 {
34 printf("\nfoo()------------------------------------->\n");
35 A localA;
36 static B localB;
37 printf("foo()<---------------------------------------\n");
38 return 0;
39 }
40 int main()
41 {
42 printf("main()---------------------------------------->\n");
43 foo();
44 foo();
45 printf("main()<----------------------------------------\n");
46
47 return 0;
48 }
编译运行:
[hongfuhao@hongfuhao task]$ g++ -o object object.cpp
[hongfuhao@hongfuhao task]$ ./object
A created
B created //main函数之前全局对象已被创建
main()---------------------------------------->
foo()------------------------------------->
A created //创建时调用构造函数
B created
foo()<---------------------------------------
A destroyed //局部对象在函数退出时被释放 静态对象并未被释放
foo()------------------------------------->
A created
foo()<---------------------------------------
A destroyed
main()<----------------------------------------
B destroyed //main函数退出后调用析构函数,全局对象与静态对象被释放
B destroyed
A destroyed
[hongfuhao@hongfuhao task]$
作用域是由{}定义,并不一定是整个函数
通过new创建的对象,但容易造成内存泄露。通过new创建的对象一直存在,知道被delete销毁
隐藏在中间的临时问变量的创建和销毁,生命周期很短,容易造成问题;(例如 :拷贝构造函数)
代码例子:
14 #include <stdio.h>
15 class A
16 {
17 public:
18
19 A(){printf("A created\n");}
20 ~A(){printf("A destroyed\n");}
21 A(const A& a){printf( "A created with a copy\n");} //拷贝构造函数
22 };
23
24 A *foo(A a)
25 {
26 printf("foo--------------->\n");
27 A *p =new A();
28 printf("foo<---------------\n");
29 return p;
30 }
31
32 A *boo(const A& a){
33 A *p= new A();
34 printf("boo<--------------\n");
35 return p;
36 }
37 int main()
38 {
39 printf("main--------------------->\n");
40 A a;
41 A *p=foo(a);
42
43 delete p;
44 p=NULL;
45
46 p=boo(a);
47 delete p;
48 p=NULL;
49
50 printf("main()<---------------------\n");
51
52 return 0;
53 }
编译运行:
[hongfuhao@hongfuhao task]$ g++ -o intebjc intebjc.cpp
[hongfuhao@hongfuhao task]$ ./intebjc
main--------------------->
A created //40行代码创建一个对象a
A created with a copy //41行代码,调用函数foo,传参数时传进去的是栈中a的一个副本,需要调用拷贝构造函数
foo--------------->
A created //局部对象a的创建
foo<---------------
A destroyed //副本与局部对象的销毁,生命周期结束 自动释放
A destroyed
A created //调用函数boo,在函数内部创建一个局部对象a,调用构造函数
boo<--------------
A destroyed//局部对像被销毁,调用析构函数 自动释放
main()<---------------------
A destroyed //40行的对象a被销毁
[hongfuhao@hongfuhao task]$
补充一点:
拷贝构造函数的额调用:
当定义一个对象,并用同类型的对象去初始化该对象时,调用构造函数:
将该类型的对象传递给函数或从函数返回该类型的对象时,隐式调用拷贝构造函数;