目录
1.内存区域
2.void与void*
3.应用场景
4.malloc
5.calloc
6.realloc
7.free崩溃的原因
7.1引入
7.2具体原因
7.2.1越界
7.2.2指针移动
7.2.3重复释放同一段内存
1.内存区域
局部变量:定义在函数内部的变量,包括形参,在栈(stack)中,作用域在函数内部有效,生存周期:进入函数创建,退出函数销毁。
栈:内存空间,局部变量所在的内存区域,系统自行管理内存的分配和回收,容量小(1M,不同的系统存在差异)
堆:内存空间,动态内存所在的内存区域,由程序(员)管理内存的分配和回收(释放),容量大(1G,不同的系统存在差异)
2.void与void*
void:没有,只能用于返回值或参数列表,表示无返回值或无参数(这个一般省略不写)
void a;错误的
void*:通用指针或泛型指针,没有具体的数据类型的指针,不能[],+i等运算,使用时需要强制类型转换
3.应用场景
4.malloc
动态申请内存,需要引用stdlib.h,没有默认值,具体参考帮助手册。
应用场景1.
//1.需要根据变量作为长度定义数组
int main()
{
int n = 10;
//int arr[n];//error,变量不能作为数组的长度,C99合法
int* p = (int*)malloc(n * sizeof(int));//创建成功后,p类似数组名
assert(p != NULL);
if (p == NULL)
{
perror("出错了");
return 0;
}
for (int i = 0; i < n; i++)
p[i] = i;
for (int i = 0; i < n; i++)
printf("%d ", p[i]);
return 0;
}
应用场景2.
错误用法:
如下代码,如果返回值为数组,数组是局部变量,函数结束后,系统自行对变量进行回收,这时return str,返回变量的地址,地址仍存在,并没有销毁,所以在主函数中打印函数的返回值时,打印的是函数传的地址,a返回函数传的地址取字符串,此时字符串为随机值,函数内的数据已经随函数的结束而销毁。
char* Getstr()
{
char str[] = "hello world";//函数结束后,系统自行对变量进行回收
return str;//返回变量的地址,地址仍存在,并没有销毁
}
int main()
{
char* a = Getstr();
printf("%s", a);//乱码
return 0;
}
辨析:返回值为普通变量时为什么不用动态内存?
如下代码,在函数中返回的是整型a的具体值,而不是它的地址。访问函数时直接将局部变量x赋值为a的值,不会再跳转到a的地址,再取a的值。
int Fun1() {
int a = 10;
return a;//合法,返回的是a的值,而不是地址
}
int main() {
int x=Fun1();
printf("%d ", x);
return 0;
}
正确写法如下,使用动态内存:
动态内存,由程序员自行销毁,可以用来函数传参,使用完成后再释放。
char* GetStr()
{
int len = strlen("hello world");
//char* str = (char*)malloc(len * sizeof(char));//常见错误
char* str = (char*)malloc((len + 1) * sizeof(char));
assert(str != NULL);
strcpy(str, "hello world");
return str;
}
int main()
{
char* p = GetStr();
printf("%s\n", p);
free(p);
return 0;
}
int main()
{
//定义1000000长度的int数组
//int arr[1000000];//不能定义这么大的数组
//int* arr = (int*)malloc(1000000 * sizeof(int));//ok
//char* arr = (char*)malloc(1024 * 1024 * 1024);//1G,ok
char* arr = (char*)malloc(1024 * 1024 * 1020 * 2);//20亿字节,2G失败
if (arr == NULL)
perror("出错了");
assert(arr != NULL);
printf("好了\n");
getchar();
free(arr);
return 0;
}
5.calloc
int main()
{
int n = 10;
int* arr = (int*)calloc(n, sizeof(int));
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);//输出n个 0
free(arr);
return 0;
}
6.realloc
扩大内存函数,具体参考帮助手册 (通常情况,扩容后地址会改变而不是在原来的地址上进行扩容,与操作系统的决策有关)
int main()
{
char *str;
/* 最初的内存分配 */
str = (char *) malloc(15);
strcpy(str, "runoob");
printf("String = %s, Address = %p\n", str, str);
/* 重新分配内存 */
str = (char *) realloc(str, 25);
strcat(str, ".com");
printf("String = %s, Address = %p\n", str, str);
free(str);
return(0);
}
7.free崩溃的原因
7.1引入
根据以往的编程经验,在使用函数传递数组时,形参包括数组的首地址arr和数组的长度len,因为传递的是首地址,是一个指针,无法在函数内部根据sizeof(arr)/sizeof(数组类型)求得数组的长度,所以形参必须包括数组的长度。
但是在使用free()释放内存空间时,只传递了数组的首地址,并没有传递数组的长度,因为在申请动态内存时,这段内存的头尾会分别生成标记,标记也占一定的内存,所以不需要传长度信息,但是在操作时如果不小心破坏了这个标记,在释放内存时就会发生错误。
int main() {
int n = 10;
int* arr = (int*)malloc(n * sizeof(int));
arr = (int*)realloc(arr, 2 * n * sizeof(int));//arr接收新的地址
free(arr);//没有传长度,申请的内存头尾会有标记占一定的内存,不需要传长度信息
return 0;
}
7.2具体原因
- 越界
- 指针移动
- 重复释放同一段内存
- 释放不是动态创建的内存
7.2.1越界
越界会损坏所申请空间的结尾标志。文章来源:https://www.toymoban.com/news/detail-743545.html
//1.越界
int main()
{
int n = 10;
int* arr = (int*)malloc(n);
assert(arr != NULL);
for (int i = 0; i < n; i++)
arr[i] = i;
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
free(arr);
return 0;
}
int main() {
int n = 10;
int* arr = (int*)malloc(n * sizeof(int));
assert(arr != NULL);
for (int i = 0; i <= n; i++)//越界,申请空间的损坏结尾标志
arr[i] = 0;
free(arr);//程序崩溃
return 0;
}
7.2.2指针移动
指针为数组首地址,指针移动了,在释放时就找不到所申请空间的头部信息。文章来源地址https://www.toymoban.com/news/detail-743545.html
//指针移动
int main() {
int n = 10;
int* arr = (int*)malloc(10 * sizeof(int));
for (int i = 0; i < n; i++) {
*arr = 0;
arr++;//指针移动了,释放时找不到头部信息
}
free(arr);//崩溃
return 0;
}
7.2.3重复释放同一段内存
//重复释放同一段内存
int main()
{
int n = 10;
int* arr = (int*)malloc(n * sizeof(int));
assert(arr != NULL);
for (int i = 0; i < n; i++)
{
arr[i] = i;
}
printf("%p\n", arr);
free(arr);
printf("%p\n", arr);//arr是野指针
free(NULL);//可以
//free(arr);//崩溃,重复释放
return 0;
}
到了这里,关于动态内存malloc,calloc,realloc如何使用,使用场景及使用free释放内存时崩溃的原因的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!