为什么需要动态内存分配?
我们一般的内存开辟方式:
int val = 10;//在栈空间上开辟4个字节
char arr[20] = { 0 };//在栈空间上开辟20个字节
这种方式开辟的空间大小是固定的,它所需要的空间在编译时分配。
但在实际需求中,有时我们的需要的空间大小在程序运行时才知道。
为了能自由调整空间大小,我们就需要动态内存开辟了。
相关函数介绍
malloc和free
头文件:stdlib.h
常用的开辟空间的函数malloc
void* malloc (size_t size);
- 开辟一个大小为
size
字节的内存块,返回指向该内存块开头的指针。
- 如果开辟失败,则返回NULL,因此malloc的返回值一定要做检查。(如不检查,vs会报出警告:取消对 NULL 指针“p”的引用。)
- 新分配的内存块的内容未初始化,里面的值不确定。
- 如果
size
为0,则返回值取决于编译器(可能是NULL也可能不是NULL)。
与之相应的释放动态内存函数free
void free (void* ptr);
- 释放通过调用
malloc
、calloc
或realloc
分配的内存块。- 如果
ptr
不指向使用上述函数分配的内存块,则会导致未定义的行为。- 如果==
ptr
是空指针==,则函数不执行任何操作。- 注意:此函数==不会更改
ptr
==本身的值,因此它仍然指向原来的位置(但是不能再使用)。
例子🌰
int main()
{
//开辟
int* p = (int*)malloc(40);//因为返回的是void*,所以可以根据需要进行转换,以便解引用
if (p == NULL)//检查
{
printf("%s", strerror(errno));
return 0;
}
//使用
//释放
free(p);
p = NULL;//是否有必要?
return 0;
}
p=NULL是否有必要?
由于free函数不会改变指针的值,因此在把它所指向的空间释放后,该指针成为野指针,安全起见,应该置为空。
calloc
void* calloc (size_t num, size_t size);
- 为num个size字节大小的元素分配一块内存,并将其所有bit位初始化为0。
- 如果
size
为0,则返回值取决于编译器(可能是NULL也可能不是NULL)。
功能上与malloc的区别就在于会把开辟的空间初始化。
realloc
void* realloc (void* ptr, size_t size);
- 调整之前申请的空间的大小。
- 将
ptr
指向空间的大小调整为size
,返回调整后的空间的起始地址。- 如果==
ptr
是空指针==,则类似于malloc,分配一个size字节的空间,返回指向其起始位置的指针。- 如果==
size
为0==,ptr指向的空间将被释放,效果类似free一样,并返回空指针(c99返回值取决于编译器)。- 如果开辟失败,则返回一个空指针,并且
ptr
指向的内存块不会被释放,且内容不变。- 如果缩小空间,则在原空间的基础上减去高地址的空间,返回的仍是原空间的地址。
-
realloc在扩大空间时有两种情况:
-
情况1:原空间后有足够的空间
这种情况下,则直接在原空间后追加空间,追加部分空间未初始化。
-
情况2:原空间后没有足够的空间
这种情况下,函数会在堆区另外找一块合适的空间,并将原空间的数据移到新空间,这样返回的就是新空间的地址。
-
例子🌰
int main()
{
//开辟
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s", strerror(errno));
return 0;
}
//使用
//需要增容
int* ptr = (int*)realloc(p, 80);//①
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}
//释放
free(p);
p = NULL;
return 0;
}
注意:①处不可以直接写为p = (int*)realloc(p, 80);
因为一旦开辟失败,p被改为NULL,那么连原来的空间都找不到了。
常见错误总结
对空指针的解引用
例子🌰
int main()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
p = NULL;
return 0;
}
对动态开辟空间的越界访问
例子🌰
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
printf("%s", strerror(errno));
return 0;
}
for (int i = 0; i <= 10; i++)//越界访问
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
对非动态开辟内存使用free释放
例子🌰
int main()
{
int a = 0;
int* p = &a;
free(p);//×错误
return 0;
}
使用free释放动态开辟内存的一部分
例子🌰
int main()
{
int* p = (int*)malloc(4 * sizeof(int));
for (int i = 0; i < 2; i++)
{
*p = i;
p++;
}
free(p);//此时p不指向空间起始位置
return 0;
}
对同一块空间多次释放
int main()
{
int* p = (int*)malloc(4 * sizeof(int));
free(p);
free(p);
return 0;
}
忘记释放动态开辟内存(内存泄漏)
void test()
{
int* p = (int*)malloc(4 * sizeof(int));
if (p == NULL)
return 0;
//使用...
//忘记释放
}
int main()
{
test();
return 0;
}
这里p为局部变量,如果在函数内部忘记释放,走出函数后p被销毁,其所指向的动态开辟内存再也找不到,并且无法释放,这块内存将一直被占用,无法再次开辟使用,造成内存泄漏。
虽然程序运行结束会自动释放内存,但对于长期运行的程序,内存泄漏会导致内存越用越小,最终造成严重的后果。
注意:动态开辟的空间一定要记得释放,并且正确释放
相关题目
❓题目1
void getMemory(char* p)
{
p = (char*)malloc(100);
}
void test()
{
char* str = NULL;
getMemory(str);
strcpy(str, "hello world");
printf(str);
}
这段代码最终结果是什么?
答案:程序崩溃
因为函数采用传值调用的方式,动态开辟的空间并没有给到str,str依然为NULL,不符合strcpy的规则。
正确的方式:
void getMemory(char** p)
{
*p = (char*)malloc(100);
}
void test()
{
char* str = NULL;
getMemory(&str);
strcpy(str, "hello world");
printf(str);
}
❓题目2
char* getMemory()
{
char p[] = "hello world";
return p;
}
void test()
{
char* str = NULL;
str = getMemory();
printf(str);
}
这段代码最终结果是什么?
答案:随机值
p数组为局部变量,虽然其地址被成功返回了,但由于函数外这块栈区空间被回收,str成为野指针,再通过地址去访问它得到的只能是随机值。
学完了动态内存分配,我们可以写一个动态版本的通讯录。[C语言] 通讯录|静态 动态 文件 链表 多版本讲解_CegghnnoR的博客-CSDN博客
柔性数组
C99 中,结构中的最后一个成员允许是未知大小的数组,这就叫做『柔性数组』成员。
struct s1
{
int i;
int a[0];//柔性数组成员,a[0]也可写成a[]
};
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例子🌰
struct s1
{
int i;
int a[0];
};
int main()
{
//printf("%d\n", sizeof(struct s1));//该结构体大小为多少?
struct s1* p = (struct s1*)malloc(sizeof(struct s1) + 40);
if (p == NULL)
return 0;
//使用
p->i = 10;
for (int i = 0; i < 10; i++)
{
p->a[i] = i;
}
//释放
free(p);
p = NULL;
return 0;
}
sizeof返回4,因为不包括柔性数组内存。使用malloc分配内存时,sizeof(struct s1)
表示分配给int i
的大小,40才是分配给柔性数组的大小。
后续也可以使用realloc
对柔性数组的大小进行修改:
//a数组增容到80字节
struct s1* ptr = (struct s1*)realloc(p, sizeof(struct s1) + 80);
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}