对C/C++内存分配的思考与总结
2017-04-18 10:06
295 查看
下图为操作系统内存地址空间图:
在C/C++中通常可以把内存划分为:
栈、堆、自由存储区、全局(静态)存储区、常量存储区
一个C/C++编译的程序占用的内存分为以下部分:
1.栈(stack):存储局部变量,函数参数,返回地址等。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
2.堆(heap):存放动态分配(malloc)的内存。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(Operating
System)回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
//在C++中需区分堆和自由存储区(自由存储是C++中通过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操作系统的术语,是操作系统维护的一块动态分配内存。new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。堆与自由存储区还是有区别的,他们并非等价)
3.全局(静态static)区(.bss段/.data段):存放全局变量和静态变量。包含.bss段(存放未初始化或初值为0的全局变量和静态局部变量)和.data段(存放已初始化或初值非0的全局变量和静态局部变量),注意:在C++中不会去区分全局变量是否初始化,他们共同占用同一块内存区域。
4.文字常量区(.rodata段):常量字符串就放在这里的不允许修改,程序结束后由系统释放。
5.代码存储区(.text段):存放函数二进制代码。
示例代码如下:
C Code
--------------------------------------------------------------------------------------------
堆和栈的生长方向不同:
堆的生长方向是向着内存地址增加的方向,栈的生长方向是向着内存地址减小的方向。当栈和堆生长到开始相互覆盖时,则称为“栈堆冲突”,系统肯定垮台。
判断栈是由高地址向低地址生长
代码如下:
C Code
输出结果为:
fir : 0028FEFF0028FEFF
sec : 0028FEFF 0028FECF
stack grew downward
---------------------------------------------------------------------------------------------
find_stack_direction函数使用函数递归的方法
第一次进入,由于addr为NULL,所以将字符变量dummy的地址赋值给静态变量addr
第二次进入,由于静态变量addr已赋了值,所以进入 "Second entry."
接着,将第二次进入的dummy地址和第一次进入的dummy地址相比较
如果值非负,则堆栈向高地址增长;否则,堆栈向低地址增长
源代码来源:http://www.cnblogs.com/xkfz007/archive/2012/06/22/2558935.html
------------------------------------------------------------------------------------------------
对内存的基本了解后,也引发了我的一些思考:
C Code
这段代码在定义指针*x的时候并没有明确它的指向(未初始化),该指针可能指向任意一块内存,之后第五行代码则是对该处内存区域的数据进行修改,这样的操作很可能访问到非法内存导致段错误,当然也可能因为没访问到非法内存而没有产生段错误,但是一个健壮的程序不允许存在这样的隐患。所以说对数据的初始化是不可缺少的习惯。
可能出现内存泄露的几种情况:
情况一:同时当我们在使用malloc多次申请内存的时候(申请堆区),一定要进行相应的free来释放指针所指向的地址空间。如果多次malloc而只进行一次free的话,也会造成内存泄露。
情况二:malloc申请地址空间后,还未用free释放该空间时就将指向该空间的指针置为NULL。
接下来说说free这个函数,来看下面这段程序:
C Code
运行结果:
0028FF1C 3544168
0028FF1C 10
0028FF1C 3544168
我们知道free函数的用法是释放指针所指的内存空间(切记不是释放指针),然后根据运行结果我们可以推断出实际上虽然内存空间已被释放,但是该指针若不置为NULL(切记free后将指针置为NULL),则它所指向的地址空间还是刚刚已经释放的地址空间(上述程序显示指针指向的那块内存空间地址未发生改变)。
我们可以看到原本该地址空间的值为3544168,当我们修改该地址空间的值让其变为10后再释放该空间,接下来如果我们访问这块空间,会发现这块地址空间居然还是3544168,而不是10。至于为什么,我们不去追究,不同的操作系统有不同的解决方式。
其实我想说的只有两点:
(1).你可以不遵守规则,但不等于没有规则。(2).不遵守规则而产生的结果是不可预测的。
https://segmentfault.com/q/1010000000253786(这里是对free释放内存后,对于内存空间疑问的讨论)
总结:
我们应该从这些点滴看起,当我们遇到一个很大的项目时,如果我们能在这些地方做到细微的注意,那么我们将会写出健壮的代码。
----------------------------------------------------------------------------------------------
参考资料:
http://www.cnblogs.com/QG-whz/p/5060894.html(C++自由存储区不等价于堆的解释)
http://www.cnblogs.com/xkfz007/archive/2012/06/22/2558935.html(栈增长方向与大端小端问题)
http://chenqx.github.io/2014/09/25/Cpp-Memory-Management/(C++内存管理深度好文)
https://segmentfault.com/a/1190000003697054(内存分配问题)
http://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html(C++堆栈及静态数据区详解)
http://crazyof.me/blog/archives/995.html(堆栈及内存动态分配的解释,非常详细,推荐阅读!!)
https://mirsery.farbox.com/post/shu-ju-jie-gou/-zhuan-dui-he-zhan-zai-nei-cun-zhong-de-qu-bie(堆栈在内存中的区别比较)
在C/C++中通常可以把内存划分为:
栈、堆、自由存储区、全局(静态)存储区、常量存储区
一个C/C++编译的程序占用的内存分为以下部分:
1.栈(stack):存储局部变量,函数参数,返回地址等。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
2.堆(heap):存放动态分配(malloc)的内存。一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(Operating
System)回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
//在C++中需区分堆和自由存储区(自由存储是C++中通过new与delete动态分配和释放对象的抽象概念,而堆(heap)是C语言和操作系统的术语,是操作系统维护的一块动态分配内存。new所申请的内存区域在C++中称为自由存储区。藉由堆实现的自由存储,可以说new所申请的内存区域在堆上。堆与自由存储区还是有区别的,他们并非等价)
3.全局(静态static)区(.bss段/.data段):存放全局变量和静态变量。包含.bss段(存放未初始化或初值为0的全局变量和静态局部变量)和.data段(存放已初始化或初值非0的全局变量和静态局部变量),注意:在C++中不会去区分全局变量是否初始化,他们共同占用同一块内存区域。
4.文字常量区(.rodata段):常量字符串就放在这里的不允许修改,程序结束后由系统释放。
5.代码存储区(.text段):存放函数二进制代码。
示例代码如下:
C Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #include < stdio.h> #include < stdlib.h > int a = 0; //全局初始化区(.data段) char *p1; //全局未初始化区(.bss段) int main() { int b; //栈 char s[] = "abc"; //栈 char *p2; //栈 char *p3 = "123456"; //123456在常量区 p3在栈区。 static int c = 0; //全局(静态)初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20);//malloc分配的10和20字节均在堆申请 strcpy(p1, "123456");//123456放在文字常量区,编译器可能会将它与p3所指向的"123456"优化在一个内存块 return 0; } |
堆和栈的生长方向不同:
堆的生长方向是向着内存地址增加的方向,栈的生长方向是向着内存地址减小的方向。当栈和堆生长到开始相互覆盖时,则称为“栈堆冲突”,系统肯定垮台。
判断栈是由高地址向低地址生长
代码如下:
C Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #include< stdio.h > static int stack_dir; static void find_stack_direction (void) { static char *addr = NULL; //address of first'dummy', once known char dummy; //to get stack address if (addr == NULL) { //initial entry addr = &dummy; printf("fir : %p %p\n", addr, &dummy); find_stack_direction (); //recurse once } else //second entry { printf("sec : %p %p\n", addr, &dummy); if (&dummy >= addr) stack_dir = 1; //stack grew upward else stack_dir = -1; } //stack grew downward } int main(void) { find_stack_direction(); if(stack_dir == 1) puts("stack grew upward"); else puts("stack grew downward"); return 0; } |
fir : 0028FEFF0028FEFF
sec : 0028FEFF 0028FECF
stack grew downward
---------------------------------------------------------------------------------------------
find_stack_direction函数使用函数递归的方法
第一次进入,由于addr为NULL,所以将字符变量dummy的地址赋值给静态变量addr
第二次进入,由于静态变量addr已赋了值,所以进入 "Second entry."
接着,将第二次进入的dummy地址和第一次进入的dummy地址相比较
如果值非负,则堆栈向高地址增长;否则,堆栈向低地址增长
源代码来源:http://www.cnblogs.com/xkfz007/archive/2012/06/22/2558935.html
------------------------------------------------------------------------------------------------
对内存的基本了解后,也引发了我的一些思考:
C Code
1 2 3 4 5 6 7 | #include < stdio.h > int main(void) { int *x; *x = 3; return 0; } |
可能出现内存泄露的几种情况:
情况一:同时当我们在使用malloc多次申请内存的时候(申请堆区),一定要进行相应的free来释放指针所指向的地址空间。如果多次malloc而只进行一次free的话,也会造成内存泄露。
情况二:malloc申请地址空间后,还未用free释放该空间时就将指向该空间的指针置为NULL。
接下来说说free这个函数,来看下面这段程序:
C Code
1 2 3 4 5 6 7 8 9 10 11 12 13 | #include < stdio.h> #include < stdlib.h > int main() { int *p = (int *)malloc(4); printf("%p %d\n", &p, *p); *p = 10; printf("%p %d\n", &p, *p); free(p); printf("%p %d\n", &p, *p); return 0; } |
0028FF1C 3544168
0028FF1C 10
0028FF1C 3544168
我们知道free函数的用法是释放指针所指的内存空间(切记不是释放指针),然后根据运行结果我们可以推断出实际上虽然内存空间已被释放,但是该指针若不置为NULL(切记free后将指针置为NULL),则它所指向的地址空间还是刚刚已经释放的地址空间(上述程序显示指针指向的那块内存空间地址未发生改变)。
我们可以看到原本该地址空间的值为3544168,当我们修改该地址空间的值让其变为10后再释放该空间,接下来如果我们访问这块空间,会发现这块地址空间居然还是3544168,而不是10。至于为什么,我们不去追究,不同的操作系统有不同的解决方式。
其实我想说的只有两点:
(1).你可以不遵守规则,但不等于没有规则。(2).不遵守规则而产生的结果是不可预测的。
https://segmentfault.com/q/1010000000253786(这里是对free释放内存后,对于内存空间疑问的讨论)
总结:
我们应该从这些点滴看起,当我们遇到一个很大的项目时,如果我们能在这些地方做到细微的注意,那么我们将会写出健壮的代码。
----------------------------------------------------------------------------------------------
参考资料:
http://www.cnblogs.com/QG-whz/p/5060894.html(C++自由存储区不等价于堆的解释)
http://www.cnblogs.com/xkfz007/archive/2012/06/22/2558935.html(栈增长方向与大端小端问题)
http://chenqx.github.io/2014/09/25/Cpp-Memory-Management/(C++内存管理深度好文)
https://segmentfault.com/a/1190000003697054(内存分配问题)
http://www.cnblogs.com/hanyonglu/archive/2011/04/12/2014212.html(C++堆栈及静态数据区详解)
http://crazyof.me/blog/archives/995.html(堆栈及内存动态分配的解释,非常详细,推荐阅读!!)
https://mirsery.farbox.com/post/shu-ju-jie-gou/-zhuan-dui-he-zhan-zai-nei-cun-zhong-de-qu-bie(堆栈在内存中的区别比较)
相关文章推荐
- C/C++程序到内存分配个人总结
- C++小总结---内存分配
- C/C++内存分配方式总结——来自《高质量编程指南》
- c++ 指针总结 函数参数指针调用和堆栈内存的分配原理
- C++内存分配总结
- C++内存分配问题汇总(多篇博客的总结)
- C/C++程序到内存分配个人总结
- C\C++指针(地址)学习总结(附内存分配方式)
- c++基础---c/c++编程-内存分配5大区(总结)
- C++内存分配方式总结
- c++中函数中变量内存分配以及返回指针、引用类型的思考
- c++中函数中变量内存分配以及返回指针、引用类型的思考
- C/C++程序中内存的分配和堆栈的区别总结
- C/C++程序到内存分配个人总结
- C/C++程序到内存分配个人总结
- C/C++程序到内存分配个人总结
- 关于C++内存分配的一些总结
- C++内存管理——明晰C++内存分配的五种方法的区别
- c/c++ 程序内存分配
- 明晰C++内存分配的五种方法的区别