首页 > C > 内存 阅读:57,774

内存分配

在程序运行过程中,有很多数据需要保存在一个地方,然后再对数据进一步处理。比如读文件,网络数据传输与接收,凡是有数据需要处理的地方,都离不开为这些数据分配一段合适的内存。

内存分配,主要分为两种:一是在栈上分配内存;一是在堆上分配。本章将详细介绍内存分配相关的知识。

大家已经知道,在C程序中,能存放数据的地方包括:静态区,栈,堆。其中全局变量,静态局部变量以及常量字符串都存储在静态区。而函数内的局部变量则分配在栈上。通过malloc()函数分配的内存则来自于堆上。

9.1.1栈上分配

即在函数内部定义局部变量,就是在栈上分配内存。占内存大小是有限制的,不能超过栈的大小,否则会造成栈的溢出。

void func()

{

    int Number[1024]={0};//Number数组从栈上分配内存

}

栈上分配的内存系统会在函数运行结束后自动回收。

Windows在应用层的栈大小为1M,而Linux在应用层的大小为10M。那就意味着在Windows的应用层分配的栈空间大小不能超过1MLinux栈上分配的栈空间不能超过10M

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在应用层的栈大小为1M,而Linux在应用层的大小为10M。那就意味着在Windows的应用层分配的栈空间大小不能超过1MLinux栈上分配的栈空间不能超过10M

比如下面的程序:

int main(void)

{

         char buf[1*1024*1024] = {0};

         int a = 0

         return 0;

}

在程序中,buf的内存加上a的内存为1M+4个字节,已经超过了Windows栈上最大值的限制,所以发生了栈溢出。

而对于堆上分配的内存来说,大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

此外,一般来说,栈的增长方向是从高往低走,而堆上的内存增长方向是从低地址往高地址走。

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,局部变量。而堆上分配的内存中存放的数据取决于程序员自己的意图,即放什么数据由程序员自己决定。

周哥教IT,分享编程知识,提高编程技能,程序员的充电站。跟着周哥一起学习,每天都有进步。

通俗易懂,深入浅出,一篇文章只讲一个知识点。

当你决定关注「周哥教IT」,你已然超越了90%的程序员!

IT黄埔-周哥教IT技术交流QQ群:213774841,期待您的加入!

二维码
微信扫描二维码关注