文章目录
1. 内存4区
程序在运行时,指令是存放在内存中的,程序大致占用了内存大概4个区:
代码区、数据区、栈区、堆区
1.1 代码区
程序指令存放在这里,由操作系统管理
特征:共享、且只读
1.2 数据区
-
数据区
存放由const修饰的常量、字符串常量等
-
全局(静态)区
存放由static修饰、全局变量等
注意#define与const不同,#define是预处理,在编译前由编译器简单替换,而不是作为常量在程序运行时放到内存
1.3 栈区(本文重点)
存放的数据由系统管理,自动分配释放
如:函数的参数,局部变量等
1.4 堆区(本文重点)
存放的数据由开发者手动分配释放
若开发者不释放,则系统会在程序运行结束后回收内存
如:用malloc函数申请的内存空间
1.5 分4区的意义
赋予了不同类型的变量不同的生命周期。根据实际情况需要,选择不同的变量声明方式,使开发时更灵活
但是也容易带来内存泄露等隐患,需要开发者特别注意内存管理
2. 错误代码示例(悬垂指针)
// 关键代码
void fun(int ** q) {
int i = 5;
*q = &i;
}
int main() {
int * p;
fun(&p);
printf("%d\n", *p);
printf("%d\n", *p);
return 0;
}
q和&p是本身(∵函数参数的地址传递),所以这里没有用return返回*q(当然也可以用)
代码是可以运行的,运行结果是
[username@host directory]$ ./test.out
5
32766
可以看到,第一次输出的是正确的值,但是第二次输出了一个随机值,这是为什么呢?
3. 原因(悬垂指针)
通过上面内存4区的介绍,可以知道:
fun()
函数中的变量q
和i
是局部变量,存放在栈区中,栈区由系统分配释放资源。
q和&p是本身,指向同一内存单元i
。
但是在函数fun()
释放时,变量q
和i
也被释放掉了。
综上,仅剩p
指向i
的躯壳(原对象不存在了),这里p
就成了悬垂指针。
悬垂指针指向的内存单元已经不属于该程序(堆区的东西被系统释放),再操作往往会导致程序错误,而且难以检测。
但是为啥第一次输出时是正确的值5
呢?
因为系统会为程序暂时保留值,但是这一次使用后就会彻底清除了,所以看到第二次取到的值是一个随机数。
4. 如何避免
不要返回局部变量地址!!!
不要返回局部变量地址!!!
不要返回局部变量地址!!!
如果要使用别个函数中局部变量地址,记得利用堆区!!!
堆区的内存区域是由开发者手动分配释放的,在手动释放前可以随意使用
4.1 C语言
利用动态内存管理的几个函数
-
malloc()
分配指定字节数的内存区域,初始化为0,返回首地址的void指针(可强制转换后再使用)
// 分配连续的一段内存空间
// 大小为400字节(这里设int为4字节)
// 强制转换成int *类型的指针(同整形数组),例++p时,p指针向后移4个字节
p = (int*) malloc(100 * sizeof(int));
-
calloc()
类似
malloc()
,但不初始化
// 分配连续的一段内存空间
// 大小为25个int类型大小的区域
// 强制转换成int *类型的指针(同整形数组),例++p时,p指针向后移4个字节
p = (int*) calloc(25, sizeof(int));
-
realloc()
更改以前分配的内存的大小
// 以x的大小,形同当前p指针,重新分配内存
p = realloc(p, x);
-
free()
释放已分配的内存区域
free(p);
// 之后再p再次被分配内存之前,不能再使用
4.2 C++
4.2.1 使用new和delete
例,使用new
为数组申请内存空间
// 分配5个int类型元素的数组空间
// 并将前三个元素初始化11、22、33
int *p = new int[5]{11, 22, 33};
4.2.2 使用智能指针
在C++11之后的版本,包含头文件<memory>后,可使用auto_ptr、unique_ptr、shared_ptr和weak_ptr四个智能指针
4. 总结
-
局部变量在程序运行时存放在栈区,在函数释放空间会被系统收回。此时再操作会引起程序错误
-
不要返回局部变量地址!!!
-
如果要使用其他函数中局部变量地址,要利用堆区!!!