这两种情况都需要用到本节要讲的动态内存,因为动态内存分配在堆中,该 区域非常大,在 Windows 平台默认大于 1.2G。
1.需要通过变量定义数组长度;
2.需要定义大容量的数组。
C 语言创建动态内存的有三个函数:malloc,calloc,realloc。都需要引用 stdlib.h 文件。
malloc
函数原型为: void *malloc(unsigned int size);
作用:给函数传递一个非负的整数 size,那么函数在内存的动态存储区(堆) 中分配一个长度为 size 字节的连续空间。
返回值: 成功时返回动态内存的首地址。失败时返回 NULL。失败只有一种 情况就是申请的内容太大,超出堆能提供的最大连续空间。
一种比较保守的编程方式是:每次都验证动态申请内存的返回值是否为空。
void *:不特指某一种类型的指针,只用来保存地址值。
例如需要动态申请 n 个字节的程序。
calloc
函数原型为:void *calloc(unsigned int num , unsigned int size);
参数含义:num,申请单元个数;size:每个单元占用的字节数。
作用:在内存的动态存储区(堆)中分配一个长度为num乘以size字节的连续空间。
返回值:成功时返回动态内存的首地址。失败时返回 NULL,失败只有一种情 况就是申请的内容太大,超出堆能提供的最大连续空间。
calloc 与 malloc 最大的区别是:calloc 会将每个元素的值置为 0。
realloc
该函数主要用于扩展内存。
其函数原型为:void *realloc(void * memblock , unsigned int size);
参数的含义:memblock,旧内存的地址; size,新申请的内存大小,以字节为 单位。
作用:在原有的动态内存的基础上进行扩容。
返回值:成功时返回新的动态内存的首地址。失败时返回 NULL,失败只有一 种情况就是申请的内容太大,超出堆能提供的最大连续空间。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
int n = 10;
int *p = (int *)malloc(n*sizeof(int));//注意乘以 sizeof(int)
assert(p != NULL);
for(int i=0;i<n;i++) //模拟数组的使用
{
p[i] = 1;
}
free(p); //使用完,释放内存
return 0;
}
动态内存如果只申请,使用后不释放则会出现内存泄漏的问题。即内存被申 请出去,但是一直不还,导致能用的内存越来越少。C 语言中有两大棘手的问题,1. 数组越界;2.内存泄漏。数组越界的问题是越界后把数组周边的数据非法修改了, 而这个数据是什么并不统一,那么出现的问题千奇百怪,不容易分析。内存泄漏就 是能用的内存越来越少,它不会让程序直接崩溃,而像温水煮蛙,程序呈现越来越 慢的问题,当你能直观察觉到慢时可能需要几个月,几年的时间(比如你的手机)。 程序员找问题都是需要调试,而当程序重新开始调试时意味着程序又重新开始运 行了,这时它健步如飞,没有任何问题。内存泄漏这类问题无法通过调试来分析。
泄漏的内存是否永远都不能再被使用呢?不是,程序泄漏的内存在如下情况 是会被还回来的。 1.整个程序(进程)退出,那么这个程序(进程)分配的内存全部会被回收回来。
2.操作系统关机,那么所有的内存都被回收。
所以平常我们写程序很难察觉内存泄漏这个问题就是因为程序运行的时间 不够长,即使有内存泄漏也察觉不到。
free(void *memblock);
C 语言中用来释放动态申请内存的函数。参数只有一个就是动态申请的内存。
有一个工具可以用来检查内存泄漏-” visual leak detector (vld)”。
* 局部变量:在函数内部定义的变量
* 全局变量:在函数外部定义的变量
* 栈stack:局部变量,函数调用 1M左右
* 如果需要的数组(内存)超过1M怎么办?
* 如果需要变量作为数组的长度,在VS编译器是非法的,怎么办?
* 上面的问题都可以通过动态内存解决
* 创建动态内存: 分配区域在"堆(heap)",2G左右,很大,必须掌握,在数据结构中大量使用
* malloc: 参数"需要创建的内存大小,以字节为单位";返回值 "返回申请的内存地址,如果申请失败返回NULL",
* calloc: 将每个单元都初始化为0
* realloc:用于扩容
* 销毁(释放)动态内存:
* free:如果不释放内存,则出现内存泄漏
* 内存泄漏:内存被申请出去,但是没有被还回来,导致这段内存别的程序"再也无法申请"
* 类似从图书馆借的书,不还,被人无法再借
* 泄漏的内存什么时候可以回收:1.程序(进程)退出(如果程序一直执行,内存泄漏就是大问题.比如"打电话"程序);2.关机
* void:没有,只能在两个地方使用,一个是返回值(表示没有返回值).另一个是参数(表示无参)
void *:通用指针(泛型指针),没有特定类型的指针,仅仅用来保存地址.
* assert:断言,断定表达式一定为真,如果为假则程序崩溃,但同时会提供失败信息.
它还是"真朋友",工作中使用的非常多,在Debug中断言有效,在Release中断言全部失效
* 如果家里漏水,最麻烦的是找漏水点
* 如果程序上百万行,如何找到程序崩溃点?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
*/
//free的使用
int main()
{
int n = 10;
int* p = (int*)malloc(n * sizeof(int));
assert(p != NULL);
if (p == NULL)
return -1;
//没有free,出现内存泄漏
//free(p);
return 0;
}
/*
//屯和烫的故事
int main()
{
int n = 10;
//char* p = (char*)malloc(n * sizeof(char));//随机值,"屯"
//char arr[10];//局部变量,如果没有初始化,随机值,"烫..."
// printf("%s\n%s\n",p,arr);
return 0;
}
*/
#if 0
//realloc
int main()
{
int n = 10;
int* arr = (int*)malloc(n * sizeof(int));
assert(arr != NULL);
if (arr == NULL)
return 0;
//模拟arr被使用
for (int i = 0; i < n; i++)
arr[i] = i;
//使用arr的时候,发现arr长度不够
//需要扩容2倍, 买房 (1.找到更大的房,2,搬家;3,卖旧房;4.更新新地址)
arr = (int *)realloc(arr,2*n*sizeof(int));//todo 旧地址和新地址是否一样??
assert(arr != NULL);
if (arr == NULL)
return -1;
for (int i = 0; i < 2*n; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
#endif
#if 0
//使用calloc
int main()
{
int n = 10;//动态创建n个长度的int数组
int *arr = (int *)calloc(n,sizeof(int));//等同下面三行
/*int* arr = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++)
arr[i] = 0;*/
assert(arr != NULL);
if (arr == NULL)
return -1;
for (int i = 0; i < n; i++)
printf("%d ",arr[i]);
return 0;
}
#endif
//void Fun(void)//void的两个用法
//{
//
//}
/*
//a/b;
int Div(int a, int b)//除法
{
assert(b != 0);
if (b == 0)
return 0;
return a / b;
}
int main()
{
printf("%d\n",Div(10,2));
printf("%d\n",Div(10,0));//崩溃
return 0;
}*/
/*
//利用筛选法求n以内的素数
int main()
{
int n;
scanf("%d",&n);
//定义n长度的标记数组
//int arr[n];//error
//int arr[1000];//error,如果n>1000就会越界
int* arr = (int*)malloc(n * sizeof(int));//ok,定义标记数组arr
assert(arr != NULL);
if (arr == NULL)
return -1;
//将所有的标识置为1,默认都是素数 .标记1为素数,0为非素数
int i;
for (i = 0; i < n; i++)
arr[i] = 1;
arr[0] = arr[1] = 0;//0和1,不是素数
//筛选法,踢出非素数
for (i = 2; i < n; i++)//没有优化
{
for (int j = i + 1; j < n; j++){
if (j % i == 0)//说明j不是素数,则将j对应的标记置为0
arr[j] = 0;
}
}
for (i = 0; i < n; i++)//遍历整个标记数组,输出所有的素数
{
if (arr[i] != 0)//说明i是素数
{
printf("%d ",i);
}
}
return 0;
}
*/
#if 0
//malloc
int main()
{
int n = 10;
int *p = (int *)malloc(n*sizeof(int));//创建n个长度的int数组
assert(p != NULL);
if (p == NULL)
return -1;
for (int i = 0; i < n; i++)
p[i] = i;
return 0;
}
#endif
#if 0
//通过动态内存实现变量作为数组的长度
int main()
{
int n;
scanf("%d", &n);
size_t;
//int arr[n];//error,定义n个长度的int数组
int* arr = (int *)malloc(n*sizeof(int));//arr类似上面的数组
assert(arr != NULL);//判断arr是否申请成功,它和下面的判断总是在一起 CP
if (arr == NULL)//if是否能放在assert的上面 ? 不能
return -1;
for (int i = 0; i < n; i++)//模拟arr数组被使用
arr[i] = i;
for (int i = 0; i < n; i++)
printf("%d ",arr[i]);
/*char a;
short b;
float c;
double d;
printf("%p,%p,%p,%p\n",&a,&b,&c,&d);*/
return 0;
}
#endif
/*
//创建动态内存,创建大内存
int main()
{
//void *vp1 = malloc(1024*1024);//1M,ok
//void* vp2 = malloc(1024 * 1024 * 1024);//1G ,ok
//void* vp3 = malloc(1024 * 1024 * 1020 * 2);//2G,失败
void* vp3 = malloc(1024 * 1024 * 1024 * 1.8);//1.8G,ok
if (vp3 == NULL)
printf("失败了\n");
getchar();//从键盘获取一个输入,暂停程序,不让程序结束(该程序申请的内存不会释放)
return 0;
}
*/
/*
//根据变量定义数组,变量作为数组的长度
int main()
{
int n;
scanf("%d",&n);
int arr[n];//定义n长度的数组,C99合法,VS编译器不支持
return 0;
}
*/
/*
//通过局部变量验证栈的大小 1M
int main()
{
//char arr[1024];//1k ,ok
//char brr[1024 * 1024];//1M,程序崩溃
char crr[1024 * 900];//900k,ok
printf("好了\n");
return 0;
}
*/
/*
int Age(int n)//O(n),O(n)递归需要开辟栈帧
{
if (n == 1)
return 10;
return Age(n - 1) + 2;
}
int main()
{
printf("%d\n",Age(100000));
return 0;
}
*/