您的位置:首页 > 其它

再谈c的内存管理及指针问题

2015-07-22 11:21 423 查看

关于内存空间的分配

内存空间主要由五个部分组成代码段(.text)、数据段(.data)、BSS段(.bss),堆和栈组成,其中代码段,数据段和BSS段是编译的时候由编译器分配的,而堆和 栈是程序运行的时候由系统分配的。布局如下:



下面分别解释各段:

BSS段:用来存放程序中未初始化的全局变量和静态变量(初始化分为显式和隐式初始化,未初始化指程序员不初始化的话,自动初始化为0。)不占磁盘空间,只在运行在再用内存空间间。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配,可以分为只读数据段和读写数据段。 字符串常量等,但一般都是放在只读数据段中。

代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等,但一般都是放在只读数据段中 。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减) 。

栈 (stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变 量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以 栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。注意:栈空间是向下增长的,每个线程有一个自己的栈,在linux上默认的大小是8M,可以用ulimit查看和修改。

内存分配方式

静态存储区域分配:内存在程序编译的时候已经分配好,这块内存在程序的整个运行空间都存在(全局变量,static变量)

在栈上创建:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

常见的内存错误极其对策

1. 内存分配未成功,却使用了它

常用解决办法,如果指针p是函数的参数,进行assert(p!==null)检查。如果是malloc和new来申请内存,应该用if(p==null)等进行防错处理。

2. 内存分配虽然成功,但是尚未初始化就引用它

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

3. 内存分配成功并且已经初始化,但操作越过了内存的边界。

这种错误常发生在数组和for语句中。

4. 忘记了释放内存,造成内存泄露。

动态内存的申请和释放一定要配对。

五个原则

  【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

  【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

  【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。

  【规则4】动态内存的申请与释放必须配对,防止内存泄漏。

  【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。

指针与数组的区别

在c/c++程序中,常产生一种错觉,指针和数组可以相互替换着用。

1. 数组要么在静态存储区域被创建(如全局数组),要么在栈上创建。

数组名对应着(而不是指向)一块内存,其地址和容量在生命周期内不变,数组内容可以变。

2.指针可以随时指向任意类型的内存块,“可变”

实例程序:

void test{
char a[] = "hello";
a[0] = 'X';
cout << a << endl;   //Xello

char *p = "world";
cout << p << endl;  //world
//p[0] = 'X';
return 0;
}


注解:字符数组a在栈上创建,内容为hello,且可以改变。

指针p指向常量字符串world(位于静态存储区)。常量字符串的内容是不可以被修改的。因此p[0] = ‘X’虽然在语法上正确,但运行时程序异常退出。

计算内容容量

运算符sizeof可以计算一个变量的容量(字节数)。

void test()
{
char a[] = "hello world";
cout<<sizeof(a)<<endl;  //12

char *p = a;
cout<<sizeof(p)<<endl;  //4
}


注解:a是一个数组,其大小为12. 而p是一个指针,因此得到一个指针变量的字节数。 C /C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

*当数组用作函数参数时,该数组自动退化为同类型的指针。

*

参考文章:c语言内存管理详解

[c语言函数调用过程])(http://m.blog.chinaunix.net/uid-23069658-id-3981406.html)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: