萌新的学习笔记,写错了恳请斧正。
目录
动态内存分配的意义
直接创建变量、数组等只能在栈区空间开辟固定大小的一段空间
而我们在实际使用时对空间的需求有时不会那么死板。比方说直接创建数组的话,其长度就固定了,不可调整——如果我们需要数组的大小在程序运行时才会被确定就不行了
所以C语言中有动态内存分配的方法,使我们能够在需要时灵活的自己申请和释放空间
malloc(memory allocation)
malloc是一个开辟动态内存的函数:
#include <stdlib.h> //或者malloc.h
void *malloc( size_t size );
这个函数将会向内存堆区申请一块大小为size(单位:字节)的连续可用的空间,并且返回指向这块空间的void*类型指针,如果开辟空间失败,则返回NULL指针
- size为0是未定义的行为,其结果取决于编译器
- 由于开辟失败可能返回NULL,因此malloc的返回值一定要检查
free
与malloc相对,free函数用来释放我们申请的动态内存空间:
#include <stdlib.h> //或者malloc.h
void free( void *memblock );
- 如果参数指向的内存块不是动态开辟的,那么free函数的行为就是未定义的
- 如果参数是空指针,那么这条语句将会被忽略,什么都不会发生
malloc与free的使用示例
#include <stdio.h>
#include <stdlib.h>
int main()
{
int num = 0;
scanf("%d", &num);
int* ptr = NULL;
ptr = (int*)malloc(num * sizeof(int));
if (ptr == NULL) //判断ptr指针是否为空
{
perror("malloc");
return 1;
}
for (int i = 0; i < num; i++)
ptr[i] = i;
for (int i = 0; i < num; i++)
printf("%d ", ptr[i]);
free(ptr);
ptr = NULL;
return 0;
}
注意:在使用free函数释放动态开辟的空间后,应及时将指向这篇空间的指针置为空指针(现在这些指针指向的空间是未申请的、非法的,容易造成野指针问题),这是一个良好的习惯
calloc(contiguous allocation)
C语言中还有calloc函数,也是用来动态分配内存的:
#include <stdlib.h> //或者malloc.h
void* calloc( size_t num, size_t size );
这个函数的功能与malloc相似,在堆区动态开辟连续的num个大小为size字节的空间,但是这个函数会把开辟出来的空间全部初始化为0
使用示例
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++)
printf("%d ", p[i]);
free(p);
p = NULL;
return 0;
}
realloc(re-allocation)
有时我们要修改动态内存的大小,可以用realloc重新分配动态内存空间:
#include <stdlib.h> //或者malloc.h
void *realloc( void *ptr, size_t new_size );
ptr是要调整的内存的地址,new_size是调整后的新大小,返回值是调整之后的内存起始位置
realloc扩展内存空间时有三种情况:
- 原有空间后有足够大的空间:直接在原处追加空间,原空间数据不变(追加空间未初始化)
- 原空间后空间不足以扩展:在堆区另外找一个合适大小的连续空间,将原空间数据复制过去(增加的空间未初始化),并将原空间释放
- 找不到足够的内存空间:不释放原有内存块,返回空指针
realloc收缩内存空间时只有一种情况:将多余的部分释放
若ptr是NULL,则功能与malloc一致
若new_size为0,则行为未定义,避免这样做
使用示例
由于realloc如果重分配失败会返回NULL,如果我们要对p指针重分配,我们就不会直接拿p来接受realloc的返回值。因为如果返回NULL,原空间不仅没有被释放,也没有指针指向原空间,就造成了内存泄露,所以我们可以这样:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int* q = (int*)realloc(p, 20 * sizeof(int));
if (q == NULL)
{
perror("realloc");
return 1;
}
p = q;
free(p);
p = q = NULL;
return 0;
}
查询已分配空间大小
如果我们有一片分配好的动态内存,想知道之前到底分配了多大的空间时,我们可以使用_msize(Windows)和malloc_usable_size(Linux)函数来查询:
#include <stdlib.h> //或者malloc.h
size_t _msize( void *memblock );
size_t malloc_usable_size(void *_Nullable ptr);
这两个函数都可以返回参数指针所对应的动态内存空间实际分配的大小
注意:这个值可能比我们开辟空间时预想的大小大(具体可以看这个)
动态内存分配常见错误
1.对潜在NULL指针的解引用
void test()
{
int* p = (int*)malloc(INT_MAX);
*p = 20; //如果p的值是NULL,就会有问题
free(p);
}
这里我们试图给p分配INT_MAX(limits.h中定义的值,等于整型的最大值),这很有可能分配失败返回NULL指针,而下面对其解引用显然就是不安全的(编译器也会报警告的)
在动态分配内存后,一定要进行空指针判断
2.对动态开辟空间的越界访问
void test()
{
int i = 0;
int* p = (int*)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i; //当i是10的时候越界访问
}
free(p);
}
其中exit函数会直接终止程序并且返回其参数
而EXIT_FAILURE是定义在stdlib.h中的错误代码,表示程序未成功执行(一般值为1)
3.对非动态开辟内存使用free释放
free函数的参数除了NULL只能是动态开辟的内存
4.使用free函数释放动态内存的一部分
如果free函数的参数指向的不是动态内存的起始位置,也会报错
void test()
{
int* p = (int*)malloc(100);
p++;
free(p); //p不再指向动态内存的起始位置
}
5.多次释放同一块内存
实际上就变成了对非动态开辟内存使用free释放
6.动态开辟内存后忘记释放
造成“内存泄露”:通过某个地址申请了空间,结果不还,而这个地址可能之后找不到了。这会导致这一片开辟的内存再也无法使用,如果多次内存泄露累计起来后果很严重。
所以动态开辟的内存一定要释放
使用时常犯错误一
#include <stdio.h>
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void main()
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
return 0;
}
这段代码显然是错误的,函数传值时临时拷贝了一个形参,开辟动态内存空间后空间地址传值给了形参,而实参str并没有接收到动态内存空间,应该改为使用二级指针
使用时常犯错误二
void main()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
这段代码有可能能正常的输出world,但是实际上是错误的,str释放后没有及时置为NULL。后面又使用了str,这就是典型的野指针(非法访问内存),这里可能不报错只是因为分配的那一段空间还没有被其他内容覆盖。