动态内存是相对静态内存而言的。所谓动态和静态就是指内存的分配方式。动态内存是指在堆上分配的内存,而静态内存是指在栈上分配的内存。
前面所写的程序大多数都是在栈上分配的,比如局部变量、形参、函数调用等。栈上分配的内存是由系统分配和释放的,空间有限,在复合语句或函数运行结束后就会被系统自动释放。而堆上分配的内存是由程序员通过编程自己手动分配和释放的,空间很大,存储*。堆和栈后面还会专门讲,这里先了解一下。
传统数组的缺点
“传统数组”就是前面所使用的数组,与动态内存分配相比,传统数组主要有以下几个缺点:
1) 数组的长度必须事先指定,而且只能是常量,不能是变量。比如像下面这么写就是对的:
- int a[5];
- 而像下面这么写就是错的:
- int length = 5;
- int a[length]; //错误
2) 因为数组长度只能是常量,所以它的长度不能在函数运行的过程当中动态地扩充和缩小。
3) 对于数组所占内存空间程序员无法手动编程释放,只能在函数运行结束后由系统自动释放,所以在一个函数中定义的数组只能在该函数运行期间被其他函数使用。
而动态内存就不存在这个问题,因为动态内存是由程序员手动编程释的,所以想什么时候释放就什么时候释放。只要程序员不手动编程释放,就算函数运行结束,动态分配的内存空间也不会被释放,其他函数仍可继续使用它。除非是整个程序运行结束,这时系统为该程序分配的所有内存空间都会被释放。
所谓“传统数组”的问题,实际上就是静态内存的问题。我们讲传统数组的缺陷实际上就是以传统数组为例讲静态内存的缺陷。本质上讲的是以前所有的内存分配的缺陷。正因为它有这么多缺陷,所以动态内存就变得很重要。动态数组能很好地解决传统数组的这几个缺陷。
-------------------------------------------------------我是分界线----------------------------------------------------------
malloc和free
malloc和free是库函数,不是系统调用
#include "stdlib.h"
void * malloc(size_t size);
void free(void* pointer);
关于malloc
malloc函数的详解使用
那么动态内存是怎么造出来的?在讲如何动态地把一个数组造出来之前,我们必须要先介绍 malloc 函数的使用。
malloc 是一个系统函数,它是 memory allocate 的缩写。其中memory是“内存”的意思,allocate是“分配”的意思。顾名思义 malloc 函数的功能就是“分配内存”。要调用它必须要包含头文件<stdlib.h>。它的原型为:# include <stdlib.h>
void *malloc(unsigned long size);malloc 函数只有一个形参,并且是整型。该函数的功能是在内存的动态存储空间即堆中分配一个长度为size的连续空间。函数的返回值是一个指向所分配内存空间起始地址的指针,类型为 void*型。
简单的理解,malloc 函数的返回值是一个地址,这个地址就是动态分配的内存空间的起始地址。如果此函数未能成功地执行,如内存空间不足,则返回空指针 NULL。
“int i=5;”表示分配了 4 字节的“静态内存”。这里需要强调的是:“静态内存”和“静态变量”虽然都有“静态”两个字,但是它们没有任何关系。不要以为“静态”变量的内存就是“静态内存”。静态变量的关键字是 static,它与全局变量一样,都是在“静态存储区”中分配的。这块内存在程序编译的时候就已经分配好了,而且在程序的整个运行期间都存在;而静态内存是在栈中分配的,比如局部变量。
那么,如何判断一个内存是静态内存还是动态内存呢?凡是动态分配的内存都有一个标志:都是用一个系统的动态分配函数来实现的,如 malloc 或 calloc。
calloc 和 malloc 的功能很相似,我们一般都用 malloc。calloc 用得很少,这里不做讲解,有兴趣的话可自行查阅。
如何用 malloc 动态分配内存呢?比如:
- int *p = (int *)malloc(4);
它的意思是:请求系统分配 4 字节的内存空间,并返回第一字节的地址,然后赋给指针变量 p。当用 malloc 分配动态内存之后,上面这个指针变量 p 就被初始化了。
需要注意的是,函数 malloc 的返回值类型为 void* 型,而指针变量 p 的类型是 int* 型,即两个类型不一样,那么可以相互赋值吗?
上面语句是将 void* 型“强制类型转换”成 int*型,但事实上可以不用转换。C 语言中,void* 型可以不经转换(系统自动转换)地直接赋给任何类型的指针变量(函数指针变量除外)。
所以“int*p=(int*)malloc(4);”就可以写成“int*p=malloc(4);”。此句执行完之后指针变量 p 就指向动态分配内存的首地址了。
- malloc所分配的是一块连续的内存,参数size是所分配的内存字节数。
- malloc的返回值是void* ,具体使用的时候需要做强制类型转换。
- 当请求的动态内存无法满足的时候,malloc返回NULL,对每个从malloc返回指针都进行检查,确保它不是NULLL。
- malloc申请的动态内存中的数据是随机值,不会被初始化为0。
- malloc 实际分配的内存可能有会比请求的多。
关于free
- free的参数要么是NULL,要么是从malloc、
calloc
、realloc
返回的值。 - free用于将申请的动态内存归还给系统。
- 当 free 的参数为 NULL 时,函数直接返回。
calloc与realloc
#include "stdlib.h"
void* `calloc`(size_t num,size_t size);
void* `realloc`(void* pointer,size_t new_size);
关于calloc
-
calloc
函数的参数包含了所需元素的数量以及每个元素的字节数,根据这些值可以计算出一共所需的内存大小。 -
calloc
会将申请的内存空间初始化为0。 - 如果指向把值存储到数组中,这种操作会浪费一定的时间。
关于realloc
-
realloc
用于修改原先已经分配的内存块大小。 - 当
realloc
的第一个参数pointer为NULL时,realloc
相当于malloc。 -
realloc
用于缩小一个内存块时,该内存块的尾部的部分将会被拿掉。realloc
用于扩大内存块时,原有内容依然保留,新增加的内存块添加到原先内存块的后面,新内存并未以任何方式初始化。 - 如果原先的内存块无法改变大小,
realloc
将会分配一个正确大小的内存,并把原有的内容拷贝到新的内存块上,因此realloc
之后就不能使用指向旧内存的指针,而应该使用realloc
返回的指针。
-----------------------------------------------------我是分界线------------------------------------------------------------
常见的动态内存分配错误
对NULL指针解引用操作
int main(void)
{
int *p = (int *)malloc(sizeof(int));
*p = 20; //这里没有判断返回值
//如果返回值为NULL则会出现错误
free(p);
system("pause");
return 0;
}
对动态开辟的空间越界访问
int main(void)
{
int *p = (int *)malloc(10 * sizeof(int));
if (p == NULL)
{
exit(EXIT_FAILURE);
}
int i = 0;
for (i = 0; i <= 10; i++) // 访问越界
{
p[i] = i;
}
free(p);
p = NULL;
system("pause");
return 0;
}
对非动态开辟的内存使用free释放
int main(void)
{
int a = 10;
int *p = &a;
free(p); // p不是动态申请的内存
system("pause");
return 0;
}
使用free释放一块动态开辟内存的一部分
int main(void)
{
int *p = (int *)malloc(10 * sizeof(int));
if (p == NULL)
{
exit(EXIT_FAILURE);
}
p++;
free(p); // 释放部分动态申请的内存
system("pause");
return 0;
}
对同一块内存多次释放
int main(void)
{
int *p = (int *)malloc(sizeof(int));
if (p == NULL)
{
exit(EXIT_FAILURE);
}
free(p);
free(p); // 第二次释放
system("pause");
return 0;
}
动态开辟的内存忘记释放(内存泄漏)
int main(void)
{
int *p = (int *)malloc(sizeof(int) * 10);
if (p == NULL)
{
exit(EXIT_FAILURE);
}
system("pause");
return 0;
}