学习c内存首先要明白,在C语言中,内存管理的知识点是有很多的,就像什么函数、作用域、指针等,这些知识点你是需要掌握的,不会的可以在复习复习,接下来我们进入正题:C内存管理。
1.内存一般分为4区
在计算机中内存一般都是分区来管理的,程序和程序之间的内存是独立的,不能互相访问。就像我们常用的微信和浏览器分别所占的内存区域是不能相互访问的。并且,每个程序的内存也是分区来管理的,一般可以分为很多区域,现在我们就来了解内存四区:
由上面的图可以明显的看出来内存分的4个区,代码区、静态区、栈区、堆区,就从这四个方面来学习内存管理。
静态区
静态区存放程序中所有的全局变量和静态变量。
栈区
栈(stack)是一种先进后出的内存结构,所有的自动变量、函数形参都存储在栈中,这个动作由编译器自动完成,我们写程序时不需要考虑。栈区在程序运行期间是可以随时修改的。当一个自动变量超出其作用域时,自动从栈中弹出。
· 每个线程都有自己专属的栈;
· 栈的最大尺寸固定,超出则引起栈溢出;
· 变量离开作用域后栈上的内存会自动释放。
1:观察代码区、静态区、栈区的内存地址
#include "stdafx.h"
int n = 0;
void test(int a, int b)
{
printf("形式参数a的地址是:%d\n形式参数b的地址是:%d\n",&a, &b);
}
int _tmain(int argc, _TCHAR* argv[])
{
static int m = 0;
int a = 0;
int b = 0;
printf("自动变量a的地址是:%d\n自动变量b的地址是:%d\n", &a, &b);
printf("全局变量n的地址是:%d\n静态变量m的地址是:%d\n", &n, &m);
test(a, b);
printf("_tmain函数的地址是:%d", &_tmain);
getchar();
}
运行结果如下:
结果分析:自动变量a和b依次被定义和赋值,都在栈区存放,内存地址只相差12,需要注意的是a的地址比b要大,这是因为栈是一种先进后出的数据存储结构,先存放的a,后存放的b,形象化表示如上图(注意地址编号顺序)。一旦超出作用域,那么变量b将先于变量a被销毁。这很像往箱子里放衣服,最先放的最后才能被拿出,最后放的最先被拿出。
//实验二:栈变量与作用域
#include "stdafx.h"
//函数的返回值是一个指针,尽管这样可以运行程序,但这样做是不合法的,因为
//非要这样做需在x变量前加static关键字修饰,即static int a = 0;
int *getx()
{
int x = 10;
return &x;
}
int _tmain(int argc, _TCHAR* argv[])
{
int *p = getx();
*p = 20;
printf("%d", *p);
getchar();
}
这段代码没有任何语法错误,也能得到预期的结果:20。但是这么写是有问题的:因为int *p = getx()中变量x的作用域为getx()函数体内部,这里得到一个临时栈变量x的地址,getx()函数调用结束后这个地址就无效了,但是后面的*p = 20仍然在对其进行访问并修改,结果可能对也可能错,实际工作中应避免这种做法,不然怎么死的都不知道。不能将一个栈变量的地址通过函数的返回值返回,切记!
另外,栈不会很大,一般都是以K为单位。如果在程序中直接将较大的数组保存在函数内的栈变量中,很可能会内存溢出,导致程序崩溃(如下实验三),严格来说应该叫栈溢出(当栈空间以满,但还往栈内存压变量,这个就叫栈溢出)。
//实验三:看看什么是栈溢出
int _tmain(int argc, _TCHAR* argv[])
{
char array_char[1024*1024*1024] = {0};
array_char[0] = 'a';
printf("%s", array_char);
getchar();
}
怎么办?这个时候就该堆出场了。
代码区
程序被操作系统加载到内存的时候,所有的可执行代码(程序代码指令、常量字符串等)都加载到代码区,这块内存在程序运行期间是不变的。代码区是平行的,里面装的就是一堆指令,在程序运行期间是不能改变的。函数也是代码的一部分,故函数都被放在代码区,包括main函数。
注意:"int a = 0;"语句可拆分成"int a;"和"a = 0",定义变量a的"int a;"语句并不是代码,它在程序编译时就执行了,并没有放到代码区,放到代码区的只有"a = 0"这句。
堆区
堆(heap)和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。更重要的是堆是一个大容器,它的容量要远远大于栈,这可以解决上面实验三造成的内存溢出困难。一般比较复杂的数据类型都是放在堆中。但是在C语言中,堆内存空间的申请和释放需要手动通过代码来完成。对于一个32位操作系统,最大管理管理4G内存,其中1G是给操作系统自己用的,剩下的3G都是给用户程序,一个用户程序理论上可以使用3G的内存空间。堆上的内存必须手动释放(C/C++),除非语言执行环境支持GC(如C#在.NET上运行就有垃圾回收机制)。那堆内存如何使用?
接下来看堆内存的分配和释放:
malloc与free
malloc函数用来在堆中分配指定大小的内存,单位为字节(Byte),函数返回void *指针;free负责在堆中释放malloc分配的内存。malloc与free一定成对使用。看下面的例子:
//实验四:解决栈溢出的问题
#include "stdafx.h"
#include "stdlib.h"
#include "string.h"
void print_array(char *p, char n)
{
int i = 0;
for (i = 0; i < n; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
char *p = (char *)malloc(1024*1024*1024);//在堆中申请了内存
memset(p, 'a', sizeof(int) * 10);//初始化内存
int i = 0;
for (i = 0; i < 10; i++)
{
p[i] = i + 65;
}
print_array(p, 10);
free(p);//释放申请的堆内存
getchar();
}
运行结果为:
用来在堆中申请内存空间的函数还有calloc和realloc,用法与malloc类似。
内存管理的学习还是比较难得,所以需要多多练习才好,而且也要遵循一定的原则:
· 要知道数据占多少内存,数据量的大小要知道,如果需要动态创建数组,则用堆
这是学习内存管理必须要了解的。