动态内存申请

动态分配内存的概述

动态内存分配就是在堆区开辟空间。

如果想动态分配数组的大小,必须使用动态内存分配。

静态分配、动态分配

静态分配

  1. 在程序编译或运行过程中,按事先规定的大小分配空间。
  2. 必须事先知道所需空间的大小。
  3. 分配在栈区或全局变量区。
  4. 按计划分配。

动态分配

  1. 在程序运行过程中,工具需要自由分配空间。
  2. 按需分配
  3. 分配在堆区,一般使用特定的函数进行分配。

动态内存分配函数

与动态内存分配相关的函数有 4 个:mallocfreecallocrealloc.

malloc

1
2
3
4
5
6
7
8
#include <stdlib.h>
void *malloc(unsigned int size);
功能:在堆区开辟指定长度的空间,并且空间是连续的
参数:
size:要开辟的空间的大小
返回值:
成功:开辟好的空间的首地址
失败:NULL

注意:

  1. 在调用 malloc 函数知乎,一定要判断一下,是否申请内存成功。
  2. 如果多次用 malloc 函数申请内存,第 1 次和第 2 次申请到的内存不一定是连续的。
  3. malloc 函数的返回值是 void*,所以在调用函数时要根据实际情况进行强制类型转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

char *fun() {

char *str = (char *)(malloc(100 * sizeof(char)));

str[0] = 'h';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '\0';

return str;
}

int main(int argc, char *argv[]) {
char *p = fun();
printf("p = %s\n", p);
return 0;
}

输出结果:

image

free

1
2
3
4
5
6
7
#include <stdlib.h>
void free(void *ptr)
功能:释放堆区的空间
参数:
ptr:开辟后使用完毕的堆区的空间的首地址
返回值:

注意:

  1. free 函数只能释放堆区的空间,其他区域的空间无法使用 free.
  2. free 释放的空间必须是使用 malloccalloc 或者 realloc 函数申请的空间,不能释放部分空间。
  3. 当使用 free(p) 释放 p 指向的空间后,p 还是会指向原来的地方,但原来的地方的空间已经被释放了,所以不能在使用,p 就成了野指针,所以一般来说,在释放完空间之后,要将指针置空,即 p = NULL.
  4. 一块空间只能释放一次,不能多次释放。

calloc

1
2
3
4
5
6
7
8
9
#include <stdlib.h>
void * calloc(size_t nmemb, size_t size);
功能:在堆区申请指定大小的空间
参数:
nmemb:要申请的空间的块数
size:每块的字节数
返回值:
成功:申请空间的首地址
失败:NULL

注意:malloc 函数和 calloc 函数都是用来申请内存的。

区别:

  1. 函数名字别一样;
  2. 函数参数个数不一样;
  3. malloc 申请的内存,内存中的内容是随机的,而 calloc 函数的申请的内存中的内存为 0。

例如:

1
char *p = (char*)calloc(3, 100);

在堆区申请了 3 快空间,每块的大小为 100 个字节,共 300 个字节的连续空间。

realloc

1
2
3
4
5
6
7
8
#include <stdlib.h>
void* realloc(void *s, unsigned int newsize);
功能:在原本申请好的堆区空间的基础上重新申请内存,新的空间大小为函数的第二个参数如果原本申请好的空间的后面不足以增加指定的大小,系统会重新找一个足够大的位置开辟指定的空间,然后将原本空间中的数据拷贝过来,然后释放原本的空间。如果 newsize 比原先的内存小,则会释放原先内存的后面的存储空间,只留前面的 newsize 个字节
参数:
s:原本开辟好的空间的首地址
newsize:重新开辟的空间的大小
返回值:
新的空间的首地址

增加空间:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
char *p = (char *)malloc(100);
// 想在100个字节后面追加50个字节
p = (char *)realloc(p, 150); // p指向的内存的新的大小为150个字节
return 0;
}

减少空间:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
char *p = (char *)malloc(100);
// 想重新申请内存,新的大小为50个字节
// p指向的内存的新的大小为50个字节,100个字节的后50个字节的存储空间就被释放
p = (char *)realloc(p, 50);
return 0;
}

内存泄漏

内存泄漏是指,申请的空间的首地址丢失了,再也找不到了,这块内存没法用,也没法释放,则称这块内存被泄漏了。

案例 1:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
char *p = (char *)malloc(100);
// 接下来,可以用p指向的内存了
p = "hello world"; // p指向别的地方了,保存字符串常量的首地址
// 从此以后,再也找不到你申请的100个字节了。则动态申请的100个字节就被泄露了
return 0;
}

案例 2:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include <stdlib.h>

void fun() { char *p = (char *)malloc(100); }

int main() {
// 每调用一次fun泄露100个字节
fun();
fun();
return 0;
}

千万注意:申请的空间,一定不要把首地址弄丢了,在不用之前一定要释放空间。