内存分配
在程序运行过程中,有很多数据需要保存在一个地方,然后再对数据进一步处理。比如读文件,网络数据传输与接收,凡是有数据需要处理的地方,都离不开为这些数据分配一段合适的内存。
内存分配,主要分为两种:一是在栈上分配内存;一是在堆上分配。本章将详细介绍内存分配相关的知识。
大家已经知道,在C程序中,能存放数据的地方包括:静态区,栈,堆。其中全局变量,静态局部变量以及常量字符串都存储在静态区。而函数内的局部变量则分配在栈上。通过malloc()函数分配的内存则来自于堆上。
9.1.1 栈上分配
即在函数内部定义局部变量,就是在栈上分配内存。占内存大小是有限制的,不能超过栈的大小,否则会造成栈的溢出。
void func()
{
int Number[1024]={0};//Number数组从栈上分配内存
}
栈上分配的内存系统会在函数运行结束后自动回收。
Windows在应用层的栈大小为
9.1.2 堆上分配
通过malloc()函数分配的内存即为堆上的内存。比如:
int *p=(int *)malloc(1024*sizeof(int));
堆上的内存系统不会自动回收,需要程序员亲自释放。使用free()函数可以释放堆上分配的内存,释放调用方式如下:
free(p);
当调用free()把p所指堆上的内存释放(回收)之后,p指向了一个无效内存地址,成为了野指针,所以需要重新将p置为NULL,如下:
p=NULL;
9.1.3 静态区分配
全局变量或者静态局部变量的内存空间是从静态区分配的内存,比如定义如下的全局变量:
int g_iNumber[1024]={0};
则g_iNumber就是在静态区获取内存。静态区获取的内存不需要自己释放,系统会在程序退出的时候自动回收。
9.1.4 堆与栈的区别
把数据存放在哪里比较好呢?栈上,还是堆上,还是静态区呢?这是一个问题。
首先,全局变量存放在静态区,在整个程序期间都有效,程序中所有函数也都可以访问,但是全局变量也有可能会造成名字冲突,也就是在一些复杂的项目中,各个模块由不同程序员开发,互不知道对方是否用了同样的全局变量,因此造成名字冲突,此外,全局变量也存在多线程安全问题。对全局变量的修改和访问需要做多线程安全考虑。因此,一个程序里不能有太多的全局变量。
除了全局变量(包括局部静态变量)存储数据是在静态区之外,剩下的就是用栈或堆来为数据分配存储的内存了。
举一个例子。写一个程序,接受用户的一个输入,然后把输入显示出来。那么用户输入的数据存放在什么地方呢?
首先可以把数据放在栈上,程序如下:
/*把数据存放在栈上*/
void main(void)
{
char buff[100] = {0};
scanf(“%s”, buff);
printf(“%s\n”,
buff);
}
也可以把数据放在堆上,程序如下:
/*把数据存放在堆上*/
void main(void)
{
char *buff = (char *)malloc(100);
if(buff == NULL)
return;
memset(buff, 0, 100);
scanf(“%s”, buff);
printf(“%s\n”,
buff);
free(buff);
}
那么,既然栈和堆都能存放数据,它们之间有什么区别呢?各自有什么优缺点呢?
1.首先从分配和释放的角度来看。栈空间由系统自动分配,比如在demo1里的char buff[100],一旦声明了一个合理的空间,系统就会分配这么大的空间,但是对于堆内存来说,比如demo2中所分配的空间就必须由程序员明确调用malloc()函数来分配指定大小的空间。而用完之后,对于分配在栈上的空间来说,不需要明确的释放,便可以由系统负责回收,而在堆上分配的空间,必须要调用free()函数明确释放。
2.从生命周期来看。栈上的空间的生命周期就是函数执行的过程,一旦函数执行完成,那么栈上的空间就会被回收;而堆上的空间,不会因为函数的结束而被回收,必须在调用了free() 函数之后才回收。比如:
/*demo 1*/
char *getMemory()
{
char buff[100] = {0};
sprintf(buff, “%s”, “hello world”);
return buff;
}
/*demo 2*/
char *getMemory()
{
char *buff = (char *)malloc(100);
if (buff == NULL)
return NULL;
sprintf(buff, “%s”, “hello world”);
return buff;
}
在demo 1里,由于buff是栈上的空间,当getMemory()函数退出之后,就会失效。所以,把一段失效的空间返回给调用者使用,是错误的。不能将栈上的空间作为函数的返回值。而由于demo2中的buff是在堆上分配的,就不存在这个问题。但是对于demo2中返回的内存,调用者在使用完了之后,就必须进行内存释放,否则就会产生内存泄漏。
3.从分配大小的角度来看。在栈上分配的大小是有限制的,栈上分配空间的总大小不能超过程序的栈的最大值。大家知道,栈的空间是有限制的。在第2章提到,Windows在应用层的栈大小为
比如下面的程序:
int main(void)
{
char buf[1*1024*1024] = {0};
int a = 0
return 0;
}
在程序中,buf的内存加上a的内存为
而对于堆上分配的内存来说,大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
此外,一般来说,栈的增长方向是从高往低走,而堆上的内存增长方向是从低地址往高地址走。
int main(void)
{
int a = 0;
int b = 0;
char *p1 = (char *)malloc(1);
char *p2 = (char *)malloc(1);
printf(“&a = %p, &b = %p, p1=%p, p2=%p\n”, &a, &b, p1, p2);
return 0;
}
4.从分配效率角度来看。栈上的空间分配由系统自动分配,因此速度很快。而堆中的空间在分配过程中,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表 中删除,并将该结点的空间分配给程序,因此堆分配的速度一般比较慢,而且还容易产生内存碎片。
从存放的数据角度来看。在栈空间里存放的数据为:形参,返回地址,老EBP,局部变量。而堆上分配的内存中存放的数据取决于程序员自己的意图,即放什么数据由程序员自己决定。