对C/C++内存分配的思考与总结

来源:互联网 发布:淘宝达人的粉丝怎么买 编辑:程序博客网 时间:2024/05/18 16:19
下图为操作系统内存地址空间图:

对C/C++内存分配的思考与总结

在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 0//全局初始化区(.data段)
char *p1; //全局未初始化区(.bss段)

int main()
{
    
int b; //栈
    char s[] "abc"//栈
    char *p2; //栈

    
char *p3 "123456"//123456在常量区 p3在栈区。

    
static int 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
#includestdio.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
;
}

这段代码在定义指针*x的时候并没有明确它的指向(未初始化),该指针可能指向任意一块内存,之后第五行代码则是对该处内存区域的数据进行修改,这样的操作很可能访问到非法内存导致段错误,当然也可能因为没访问到非法内存而没有产生段错误,但是一个健壮的程序不允许存在这样的隐患。所以说对数据的初始化是不可缺少的习惯。

可能出现内存泄露的几种情况:
情况一:同时当我们在使用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(堆栈在内存中的区别比较)
0 0