为什么存在动态内存分配
动态内存分配通俗的讲就是当想要开辟多大空间就可以开辟多大的空间。
int a = 10;
char arr[20] ={0};
通过创建变量或者创建数组的方式来在开辟空间有俩个特点:
- 空间开辟的大小是固定。
- 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
动态内存函数的介绍
void* malloc (size_t size);
Allocate memory block
Allocates a block of size bytes of memory, returning a pointer to the beginning of the block.
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
- 如果开辟成功,则返回一个指向开辟好的空间的指针。
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
- 返回值的类型是void*, 因为malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
//向内存申请10个整形的空间
int* p = malloc(10 * sizeof(int));
if (p == NULL)
{
printf("%s\n",strerror(errno));
}
else
{
int i = 0;
for ( i = 0; i < 10; i++)
{
*(p + i) = i;
}
for ( i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
return 0;
}
可以使用malloc函数在堆区开辟连续可用的内存空间,参数输入所要开辟空间的大小,单位是字节,函数会返回指向所开辟空间起始地址的指针(void*),当堆内存不够开辟内存空间失败时返回NULL。
至于所开辟的空间要如何管理可由用户强转的类型来管理。
(指针类型决定指针解引用可访问的范围)。
从整形指针变量 p 的视角来看,p 他就认为他所指向的是一个10个整型大小空间的起始地址。
void free (void* ptr);
Deallocate memory block
A block of memory previously allocated by a call to malloc, calloc or realloc is deallocated, making it available again for further allocations.
free 函数是用来释放和回收由malloc 函数,calloc 函数和realloc 函数动态分配的内存,以便后续这块内存的重新分配。
- 如果参数ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
- 如果参数ptr 是NULL指针,则函数什么事都不做。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
//向内存申请10个整形的空间
int* p = malloc(10 * sizeof(int));
if (p == NULL)
{
printf("%s\n",strerror(errno));
}
else
{
int i = 0;
for ( i = 0; i < 10; i++)
{
*(p + i) = i;
}
for ( i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
free(p) 这函数执行之后会将malloc 所申请的内存空间释放掉,归还给操作系统。如果不执行free()函数,p所指向的内存空间块将一直持续被占用知道程序退出的那一时刻这块空间才会被释放。
其实当p这块内存空间我们使用过而且后续也将不会被使用到,我们应该其实的将他·释放掉,因为如果程序执行的时间长,内存将不会被很合理的使用。
调用free(p) 之后,p 所指向的这块内存空间被释放,归还给了操作系统。但是p 的值本身没有发生变化,依然还是存储着原来的地址,但是这时通过p 在来访问这块空间已经是不合法的了,所以应该将p的内存置成NULL。
void* calloc (size_t num, size_t size);
num: Number of elements to allocate.
size: Size of each element.
Allocate and zero-initialize array
Allocates a block of memory for an array of num elements, each of them size bytes long, and initializes all its bits to zero.
- 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
- 与malloc 函数的区别只是在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
打印输出:0 0 0 0 0 0 0 0 0 0
calloc 函数在开辟空间后会初始化所有的比特位为0,而malloc 在开辟完之后就之间返回了,所以在速度上malloc 函数应该会更快一些。
单纯从字面就是重新开辟的意思。
void* realloc (void* ptr, size_t size);
ptr:
Pointer to a memory block previously allocated with malloc, calloc or realloc.
Alternatively, this can be a null pointer, in which case a new block is allocated (as if malloc was called).
size:
New size for the memory block, in bytes.
Reallocate memory block
Changes the size of the memory block pointed to by ptr.
return value
A pointer to the reallocated memory block, which may be either the same as ptr or a new location.
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* p = (int*)malloc(5*sizeof(int));
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i;
}
for (i = 0; i < 5; i++)
{
printf("%d ", *(p + i));
}
printf("\n");
}
int* ptr = realloc(p,10*sizeof(int));
if (ptr != NULL)
{
p = ptr;
int i = 0;
for ( i = 5; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
结果输出:
0 1 2 3 4
0 1 2 3 4 5 6 7 8 9使用realloc 函数改变之前动态分配的内存块的空间大小。 第一个参数是动态分配内存时返回的指针,第二参数是新的存块的大小。如果第一个参数是NULL 指针,那么就相当于调用malloc 一样了。
realloc 函数返回的指针可能与第一个参数相同也有可能是新的地址或者是一个NULL指针。
如果在原先的内存块后面有足够的空间,则就在原先内存块的后面扩展到新的大小,这时返回的指针还是原来的地址。
但是如果原先的内存块后面已经没有足够的空间可以继续扩展了,就只能另外重新开辟一块新空间,将原先空间的值拷贝过来,在释放到掉旧的空间,这返回的指针指向的就是新的地址了。
当然还有一种就是当新的空间很大的时候,堆内存并没有那么多可用的空闲空间可扩展,扩展失败了,那么就会返回一个NULL指针。所以我们不能使用原先的指针变量直接来接收realloc 函数返回的指针,当指针为NULL 时,之前的内存块就再也无法访问到了,之前内容也就丢失了。
常见的动态内存错误
- 对NULL指针的解引用操作
int* p = (int*)malloc(5*sizeof(int));
*p = 0;
使用malloc、calloc、realloc 函数都有可能开辟空间失败返回一个NULL指针,如果不对指针做一个判断,而是直接使用返回来的指针,就会有可能导致错误发生。
- 对动态开辟空间的越界访问
- 对非动态开辟内存使用free释放
对于free 函数是只能用来释放由malloc、calloc、realloc 等动态开辟的内存空间块。对于其他方式开辟的内存块是不适用。
- 使用free 释放动态开辟内存的一部分
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
else
{
p++;
free(p);
p = NULL;
}
return 0;
}
p++ 之后p 已经向后移动了一个整形大小,这时候使用free(p) 来释放这个动态内存块就会发生错误。
- 对同一块动态内存多次释放
- 动态开辟内存忘记释放(内存泄漏)
开辟一块内存,当程序使用这块内存过后将不会再次使用到这块内存,但是这块内存没有被释放,一直被占用之着,无法再将这块内存分配给其他对象来使用就会导致内存泄漏。如果当前程序持续的内存泄漏,那么他会占用机器大量的内存,机器的运行速度就降低,到达某个阈值的时候程序就会产生崩溃。
文章是基于比特鹏哥C语言学习视频的学习笔记,仅用于学习。请勿转载,请勿商用。