malloc/free
一、C中malloc的使用
在C中我们动态开辟内存的时候用的是malloc函数,回收时用的是free函数。
1、在C中我们一般的使用形式是这样的:
int *p = (int*)malloc(sizeof(int));
这里面存在两个问题:
(1)、sizeof(int)计算的是什么?
sizeof计算的是一个类型单元的大小
(2)、malloc之后为什么要用(int*)强转呢?
要解决这个问题,我们首先要了解malloc的原型
void* malloc(size_t size);
size_t是对ungsined int size_t类型的一个重定义
size:开辟的内存大小
void*:无符号的指针(只知道起始位置)是一个半开半闭的区间
所以这块是用(int *)强转强转成具体的类型来使用。
所以C中之所以这样使用malloc的原因可以这样总结:
首先在malloc调用点给出的实参的类型通过sizeof计算出一个类型的大小,让系统去通过malloc开辟出相对应的内存单元的大小,但是malloc开辟之后返回的是一个void 的类型。由于void类型无法直接使用,所以通过强转转成具体的类型就可以使用
2、malloc使用所出现的问题
(1)实参传递
我们如果这样写malloc函数
int* p = malloc(1);
这块在运行的时候就会报错,如下图所示:
这是因为在C中,是允许任意指针转化成void*,但是不允许void*转化成任意指针。所以我们改成这样:
int* p = (int*)malloc(1);
完整代码是这样的
int main()
{
int *p = (int *)malloc(1);
free(p);
return 0;
}
这样运行之后,我们发现程序是正确的。但是从逻辑上这段代码是不正确的。我们可以从逻辑上分析一下这段代码
- malloc接受的是开辟的内存的大小。这个时候我们赋予malloc的是一个单元的内存大小,到时候malloc只会在系统上开辟一个单位的内存大小。
- (int*)强转之后,代表这块我们定义了一个int*类型的指针P指向它,P中存放这个类型单元的地址。
- 通过P的层面,我们可以这样理解:P指向的是一个int类型的内存单元,是四个字节。
- 而malloc这个时候只赋予了1个字节的内存单元,这个时候就会发生越界的情况。
如下图所示:
之所以上面程序没有崩溃的原因就是,我们对于P没有进行任何操作,只是将它开辟后直接释放掉了。
如果代码改成这样,就会发生程序崩溃:
int main()
{
int *p = (int *)malloc(1);
*p = 20;
free(p);
return 0;
}
2、返回值类型不安全
void*本身就是类型不安全
二、free的使用
1、我们给出这样一段代码:
int main()
{
int* p = (int*)malloc(4);
double* q = (double*)malloc(8);
char* c = (char*)malloc(1);
free(p);
free(q);
free(c);
return 0;
}
这块free释放了三种类型的指针。那么问题来了,free函数中是怎么知道释放多大的内存呢?
- 所谓的内存属于冯诺依曼体系中五大部件中的存储器。但是存储器是一个硬件。但对于写的应用程序来说是无法操作硬件的。所有硬件的操作都是由操作系统操作的。
- 首先在应用程序里面写了malloc或者free。这些函数都是应用程序表层的接口。
- 调用malloc或者free的时候就相当于给操作系统发送了一个请求,由操作系统的API来接受这个请求。接受之后交给硬件驱动,由硬件驱动在内存中开辟出内存单元,最后再返给应用程序。
- 所以说我们使用malloc或者free时程序要经过操作系统。如下图所示
- 当我们调用malloc时,会在操作系统中有一个记录:开辟的内存的地址和开辟的内存的大小。
- 当我们调用free时,形参将拿到的内存地址与操作系统中产生记录的内存地址进行匹配,如果匹配成功的话,证明我们的操作是合法的。
- 匹配成功后,就获取开辟内存的大小
这就是为什么在free点,我们只需要知道释放的内存地址,而不需要知道释放的内存大小。
2、释放非法的内存空间
看这样一段代码:
int* p = (int*)malloc(sizeof(int));
p = (int*)((int)p+1);
free(p);
执行这段代码的时候我们发现程序会出错。原因如下:
- 通过malloc我们在堆上开辟了int类型的内存块,假设这块地址是0X100。
- 定义了指针p指向了这个内存单元,p里面存放的就是0x100
- 针对于第二句代码,先是对p进行类型强转。(p+1)将这块地址变为0x101.又将它转成指针类型赋给了p.
- 赋值给p后,p中存放的是0x101,p就指向了第二个内存单元
- 当调用free后free释放的是0x101的地址,在操作系统中找不到。操作系统中只有0x100.
- 所以free就释放了非法内存地址。
如下图所示:
new/delete
C++中new的使用
来说new的时候我们将new和malloc比较着来说
1、new+类型
new也不需要类型的转换。返回值类型安全
释放的时候只需要delete+数据类型即可。
int main()
{
int* p = new int;
*p = 20;
delete p;
}
2、new不仅能开辟内存还可以做初始化
类型之后加上(),()初始化列表
malloc只能单纯得开辟内存
int* p1 = new int(10);
cout << *p1 << endl;
delete p1;
3、开辟数组
C中
int* p = (int*)malloc(sizeof(int)*10);
free(p);
C++
int* p =new int[10];
delete[] p;//释放一个数组
4、处理内存不足的情况
C
int* p = (int*)malloc(sizeof(int));
if(p == NULL)
{
printf("out of memory!");
exit(0);
}
C++
int* q = new int;//抛出异常
如果在C++中出现内存不足的时候,C++直接会抛出异常,在new点退出。
5、数组初始化
int main()
{
int* p = new int[10]();//这里对数组进行了初始化0,如果不加()这时候数组中打印出来的是随机值
for(int i = 0; i < 10; i++)
{
cout << p[i] << " ";
}
cout << endl;
delete[] p;
return 0;
}
6、开辟常量堆内存
在C语言中malloc不能开辟一个常量的堆内存。
但是在C++中可以用new开辟一个常量的堆内存。
new也可以开辟一个常量的数组,但是开辟的常量数组是没有意义的,不建议使用。
int main()
{
const int* p1 = new const int(20);
delete p1;
const int* p2 = new const int[10]();
delete[] p2;
return 0;
}
7、重定位new
重定位new是通过new后面的地址在对应的内存单元拿到内存供外部使用。可以在任何位置开辟。
普通的new只能在堆上开辟内存。
int main()
{
int a;
char* c = new(&a)char('a');//replacement new
char* c1 = new char('b');//普通的new
}
new和malloc的简单区别
1、new是关键字,malloc是一个函数
2、new不需要确定开辟的大小
malloc需要传递开辟的大小
3、new返回值类型安全
malloc返回值类型不安全
4、new不仅能开辟内存,还可以做初始化
malloc单纯开辟内存
5、new[]开辟数组
malloc开辟总大小的方式来处理
6、new开辟内存失败后直接抛出异常
malloc开辟内存失败,返回NULL
7、new开辟的内存位置在自由存储区域
malloc开辟的内存位置在堆上