一、内存的组织方式
程序员编写完程序之后,程序要先加载在计算机的内存中,再运行程序。在 C语言 中,不同数据在内存中所存储的位置也不一样。全局变量存储在内存中的静态存储区,非静态的局部变量存储在内存中的动态存储区(栈)。临时使用的数据建立动态内存分配区域,需要的时候开辟,不需要时及时释放(堆)。
通过内存注释方式可以看出,堆用来存放动态分配内存空间,而栈用来存放局部数据变量、函数的参数以及调用函数与被调函数的联系。
在内存的全局存储空间中,用于程序动态分配和释放的内存块称为自由存储空间,通常也称之为堆。在 C程序 中,使用 malloc() 函数和 free() 函数来从堆中动态的分配内存和释放内存。
程序不会像处理堆那样在栈中显示地分配内存。当程序调用函数或声明局部变量时,系统将自动分配内存。
栈是一个后进先出的压入弹出式的数据结构。在程序运行时,需要每次向栈中压入一个对象,然后栈指针向下移动一个位置。当系统从栈中弹出一个对象时,最晚进栈的对象将被弹出,然后栈指针向上移动一个位置。如果栈指针位于栈顶,则表示栈是空的;如果栈指针指向最下面的数据项的后一个位置,则表示栈为满的。
二、动态管理
在头文件 stdlib.h 中声明了四个有关内存动态分配的函数。
2.1、malloc()函数
malloc() 函数的原型如下:
void* malloc( size_t size );
该函数的作用是在内存中动态分配一个 size 大小的内存空间。malloc() 函数会返回一个指针,该指针指向分配的内存空间的第一个地址,如果出现错误,则返回 NULL。
使用 malloc() 函数分配的内存空间是在堆中,而不是在栈中。因此在使用完这块内存中间之后一定要将其释放掉,释放内存空间使用的是 free() 函数。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p;
int i = 0;
p = (int*)malloc(5*sizeof(int));
printf("请输入5个整数:\n");
for(i = 0;i < 5; i++)
{
scanf("%d",p+i);
}
printf("你输入的数据是:\n");
for(i = 0; i < 5; i++)
{
printf("%d ",*(p+i));
}
free(p);
return 0;
}
2.2、calloc()函数
calloc() 函数的原型如下:
void* calloc( size_t num, size_t size );
该函数的作用是在内存中动态分配 num 个长度为 size 的连续内存空间数组,并将该内存中间的字节初始化为0。calloc() 函数会返回一个指针,该指针指向动态分配的连续内存空间的起始地址。当分配内存空间错误是返回 NULL。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *str;
str = (char*)calloc(30,sizeof(char));
printf("请输入一个字符串:\n");
gets(str);
printf("你输入的字符串为:\n");
printf("%s\n",str);
free(str);
return 0;
}
2.3、realloc()函数
realloc() 函数的原型如下:
void *realloc( void *ptr, size_t new_size );
该函数的作用是重新分配给定的内存区域。它重新分配 malloc() 函数 或 calloc() 函数 获得的动态空间大小,将 ptr 指向的动态空间大小改变为 new_size,ptr 的值不变。设定上 new_size 大小是任意的,也就是说既可以比原来的数值大,也可以比原来的数值小。返回值是一个指向新地址的指针,如果出现错误,则返回 NULL。
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *str;
str = (char*)malloc(30*sizeof(char));
str = realloc(str,15*sizeof(char));
free(str);
return 0;
}
2.4、free()函数
free() 函数的原型如下:
void free( void* ptr );
该函数的作用是释放指针 ptr 指向的内存区,使部分内存区能被其它变量使用。ptr 是最近一次调用 calloc() 函数 或 malloc() 函数时的返回值。
三、动态分配内存的基本原则
- 避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大的内存块的系统开销大;
- 仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它(如果使用动态分配内存,需要遵循谁分配,谁释放原则),否则可能出现内存泄露;
- 总是确保释放以分配过的内存。在编写分配内存的代码时,就需要确定在代码的什么地方释放内存;
- 在释放内存之前,确保不会无意中覆盖堆上已分配的内存地址,否则程序就会出现内存泄漏。在循环分配内存时,要特别小心。