前言:
常规的变量定义声明后内存空间大小就一般不变了,而有些情况是只有我们将程序运行后才知道所需的内存空间大小是多少,那么这就需要动态内存开辟,以便可以自己申请和释放空间。
目录
1.malloc函数
2.free函数
3.calloc函数
4.realloc函数
5.动态内存错误
6.柔性数组
函数原型:void* malloc(size_t size); 其中size为内存块大小,单位是字节。
头文件:
作用:函数向内存(堆区)申请一块连续可用的空间,并返回指向这块空间的指针。(开辟成功则返回开辟好的空间的起始指针;开辟失败则返回NULL指针。由于有可能返回NULL指针,所以malloc返回值一定要做检查判断)
特殊:1.void* 指针类型,需要按照使用者决定进行相应的类型转换,不能对NULL指针进行解引用;
2.size为0时,对于malloc是行为未定义的。
3.malloc函数申请的空间是在内存的堆区的,后面的free,calloc,realloc都是对于内存的堆区展开的。
使用:
#include
#include
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 10);//申请10个字节的空间大小
if (ptr == NULL) //判断
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++) //使用
{
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) //使用
{
printf("%d ", *(ptr + i));
}
free(ptr);
ptr = NULL;
return 0;
}
开辟失败的例子(需要开辟空间过大导致开辟失败):
#include
#include
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 19999999999999999);//申请19999999999999999个字节的空间大小
if (ptr == NULL) //判断
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++) //使用
{
*(ptr + i) = i;
}
for (int i = 0; i < 10; i++) //使用
{
printf("%d ", *(ptr + i));
}
free(ptr);
ptr = NULL;
return 0;
}
函数原型:void* free (void* ptr); ptr为动态开辟内存的起始地址。
头文件:
作用:用来动态内存的释放和回收。
特殊:1.参数ptr指向的空间不是动态开辟的,free行为未定义;
2.如果参数ptr是NULL指针,则函数不做任何操作;
3.free只会把动态开辟的内存释放还给操作系统,比如:
int* p = (int*) malloc(sizeof(int) * 10);
p存着开辟空间的首地址,将p释放,即free(p)后,p还存着还是原开辟空间得首地址,这是非常危险的行为,所以避免p变为野指针,一般free(p)后会将p置为NULL。
想个问题,为什么free不会将p顺便置为NULL?
因为我们将指针(值传递)传给了free函数,那么ptr为p在free函数内部的一份临时拷贝,在函数内部对ptr改变并不能对p进行改变,所以无法将ptr置为NULL进而使p为NULL。(要对p在函数内进行改变,需要传给函数一个指向p的指针,即为一个二级指针,通过解引用对p进行修改)
函数原型:void* calloc(size_t num, size_t size);
头文件:
作用:与malloc类似,进行动态内存开辟,不同的是为num个大小为size的元素开辟一块空间的同时,并把空间的每个字节初始化为0,然后返回开辟空间的起始地址。
使用:
#include
#include
int main()
{
int a = 65;
char* ptr = (char*)calloc(10, sizeof(char) * 1);
if (ptr == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++)\
{
printf("%d ", *(ptr + i));
}
putchar('\n');
for (int i = 0; i < 10; i++)
{
*(ptr + i) = a++;
}
for (int i = 0; i < 10; i++)
{
printf("%c ", *(ptr + i));
}
putchar('\n');
free(ptr);
ptr = NULL;
return 0;
}
函数原型:void* realloc(void* ptr, size_t size);
头文件:
作用:realloc能让动态内存管理更加灵活,能对动态开辟内存大小进行调整。
1.ptr为要调整的内存(动态)的起始地址;
2.size调整之后的新的内存大小;
3.返回值为调整之后的内存起始地址;
4.调整失败返回NULL指针。
realloc在调整内存空间大小存在两种情况:
1.原有空间之后有足够大的空间:在已经开辟好的空间后有足够的空间,可以直接扩大,扩大后返回原空间的起始地址(原地扩容);
2.原有空间之后没有足够大的空间:realloc函数会在内存的堆区重新找一片连续的空间(满足新的空间的大小需求),同时会把原来的数据拷贝到新的空间,然后释放原来的空间,同时返回新的空间的起始地址(异地扩容)。
#include
#include
int main()
{
int a = 65;
char* ptr = (char*)malloc(sizeof(char) * 26);
if (ptr == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 26; i++)
{
*(ptr + i) = a++;
}
for (int i = 0; i < 26; i++)
{
printf("%c ", *(ptr + i));
}
putchar('\n');
char* p = (char*)realloc(ptr, sizeof(char) * 52);
a += 6;
for (int i = 26; i < 52; i++)
{
*(p + i) = a++;
}
for (int i = 26; i < 52; i++)
{
printf("%c ", *(p + i));
}
ptr = NULL;
free(p);
p = NULL;
return 0;
}
realloc函数除了能够调整空间之外,还能实现和malloc一样的功能:
realloc(NULL,40)等价于malloc(40);
realloc规定:当参数为NULL时,则realloc进行动态开辟,与malloc的功能一样。
当然也可以缩容:
#include
#include
int main()
{
int* ptr, i;
ptr = (int*)malloc(5 * sizeof(int)); // 分配5个整型的内存空间
if (ptr == NULL)
{
printf("Memory not allocated.\n");
exit(0);
}
for (i = 0; i < 5; i++)
{
*(ptr + i) = i; // 给每个元素赋值
}
printf("Original memory block:\n");
for (i = 0; i < 5; i++)
{
printf("%d ", *(ptr + i)); // 输出每个元素的值
}
ptr = realloc(ptr, 2 * sizeof(int)); // 缩小内存块到2个整型的大小
printf("\nMemory block after shrinking:\n");
for (i = 0; i < 2; i++)
{
printf("%d ", *(ptr + i)); // 输出每个元素的值
}
free(ptr); // 释放内存
return 0;
}
常见的动态内存的错误:
1.对NULL指针解引用操作(NULL不能解引用和加减运算),所以要对返回的指针进行判断:if,assert断言……
2.对动态开辟空间越界访问;程序会崩溃
3.对非动态开辟的内存使用free;程序会崩溃
4.使用free释放一块动态开辟的空间的一部分,即不从头开始释放;程序会崩溃,所以free必须从起始地址开始释放,不能从中间开始释放。
5.对同一空间多次释放;程序会崩溃
6.内存泄漏:动态开辟内存没有释放导致内存泄漏,所以一定要正确释放开辟的内存;
malloc,calloc,realloc申请的空间,如果不主动free,出了局部作用域是不会销毁的,直到程序结束才由操作系统回收。
在c99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员。
struct s
{
int i;
int arr[];
};
//或者是这种形式
struct s1
{
int i;
int arr[0];
};
柔性数组的特点:
1.结构中的柔性数组成员前面必须至少一个其他成员;
2.sizeof返回这种结构大小时,不包括柔性数组的大小;
#include
#include
struct s
{
int i;
int arr[];
};
struct u
{
char c;
int a;
int arr2[];
};
int main()
{
printf("%zu\n", sizeof(struct s));
printf("%zu\n", sizeof(struct u));
return 0;
}
至于为什么是4和8,请移步http://t.csdnimg.cn/ybLKx
3.包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小(结构大小 + 柔性数组分配的大小),以适应柔性数组的预期大小。
具体使用:
#include
#include
struct s
{
int a;
char b;
int arr[];
};
int main()
{
struct s* ptr = (struct s*)malloc(sizeof(struct s) + 10 * sizeof(int));
if (ptr == NULL)
{
perror("malloc");
return 1;
}
ptr->a = 9;
ptr->b = 'c';
for (int i = 0; i < 10; i++)
{
ptr->arr[i] = i;
printf("%d ", ptr->arr[i]);
}
printf("\n%c%d\n", ptr->b, ptr->a);
free(ptr);
ptr = NULL;
return 0;
}
使用非柔性数组实现上面相同的功能:
#include
#include
struct s
{
int a;
char b;
int* arr;
};
int main()
{
struct s* ptr = (struct s*)malloc(sizeof(struct s));
if (ptr == NULL)
{
perror("malloc");
}
ptr->a = 9;
ptr->b = 'c';
ptr->arr = (int*)malloc(sizeof(int) * 10);
if (ptr->arr == NULL) //判断
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
ptr->arr[i] = i;
printf("%d ", ptr->arr[i]);
}
printf("\n%c%d\n", ptr->b, ptr->a);
free(ptr->arr);
ptr->arr = NULL;
free(ptr);
ptr = NULL;
return 0;
}
在结构体中定义一个指针变量来接受malloc后起始地址来实现上面柔性数组相同的效果,但要注意的是该代码需要进行两次free,并且free的顺序不能颠倒,要是先free(ptr),则ptr->arr销毁,ptr->arr指向的空间就没机会free了,接着造成内存泄漏。
该方式与柔性数组对比,柔性数组会更好的释放内存(1次释放);而且柔性数组能减少内存碎片;同时内存连续,有益于提高访问速度。
以上就是动态内存管理的全部内容,如果对你有所帮助,也请点个赞加收藏,如果存在错误或者需要补充的内容,欢迎评论区留言ヾ(≧▽≦*)o。