c++中malloc和new的区别

1、new的基本用法

C++中利用new操作符在堆区开辟数据

堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

语法: new 数据类型

利用new创建的数据,会返回该数据对应的类型的指针

示例1 基本语法

int* func()
{
	int* a = new int(10);
	return a;
}

int main() {

	int *p = func();

	cout << *p << endl;
	cout << *p << endl;

	//利用delete释放堆区数据
	delete p;

	//cout << *p << endl; //报错,释放的空间不可访问

	system("pause");

	return 0;
}

示例2:开辟数组

//堆区开辟数组
int main() {

	int* arr = new int[10];

	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}

	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	//释放数组 delete 后加 []
	delete[] arr;

	system("pause");

	return 0;
}

2、C++ new的三种面貌(底层实现)

C++中使用new运算符产生一个存在于Heap(堆)上的对象时,实际上调用了operator new()函数和placement new()函数。在使用new创建堆对象时,我们要清楚认清楚new的三种面貌,分别是:new operator、operator new()和placement new()。

1.new operator

new operator是C++保留关键字,我们无法改变其含义,但我们可以改变new完成它功能时调用的两个函数,operator new()和placement new()。也就是说我们在使用运算符new时,其最终是通过调用operator new()和placement new()来完成堆对象的创建工作。使用new operator时,其完成的工作有如下三步:
在这里插入图片描述
因此,当我们经常按照如下方式使用new operator时:

string* sp=new string(“hello world”);

实际上等价于:

//第一步:申请原始空间,行为类似于malloc

void* raw=operator new(strlen(“hello world”));

//第二步:通过placement new调用string类的构造函数,初始化申请空间

new (raw) string(“hello world”);

//第三部:返回对象指针

string* sp=static_cast<string*>(raw);

2.operator new()

operator new()用于申请Heap空间,功能类似于C语言的库函数malloc(),尝试从堆上获取一段内存空间,如果成功则直接返回,如果失败则转而去调用一个new handler,然后抛出一个bad_alloc异常。operator new()的函数原型一般为

void* operator new (std::size_t size) throw (std::bad_alloc);

具体实现如下:

void *__CRTDECL operator new(size_t size) throw (std::bad_alloc)

{   

​    // try to allocate size bytes

​    void *p;

​    while ((p = malloc(size)) == 0)   //申请空间

​       if (_callnewh(size) == 0)    //若申请失败则调用处理函数

​       {    

​           // report no memory

​           static const std::bad_alloc nomem;

​      _RAISE(nomem);        //#define _RAISE(x) ::std:: _Throw(x) 抛出nomem的异常

​    }

  return (p);

}

注意:

(1)函数后添加throw表示可能会抛出throw后括号内的异常;

(2)operator new()分为全局和类成员。当为类成员函数时,使用new产生类对象时调用的则是其成员函数operator new()。如果要重载全局的operator new会改变所有默认的operator new的方式,所以必须要注意。正如new与delete相互对应,operator new()与operator delete()也是一一对应,如果重载了operator new,那么理应重载operator delete。

3.placement new()

一般来说,使用new申请空间时,是从系统的堆中分配空间,申请所得空间的位置根据当时内存实际使用情况决定。但是,在某些特殊情况下,可能需要在程序员在指定内存空间上创建对象,这就是所谓的“定位放置new”(placement new)操作。placement new()是一个特殊的operator new(),因为其是operator new()函数的重载版本,只是取了个别名叫作placement new罢了。作用是在已经获得的堆空间上调用类构造函数来初始化对象,也就是定位构造对象。通常情况下,构造函数是由编译器自动调用的,但是不排除程序员手动调用的可能性,比如对一块未初始化的内存进行处理,获得想要的对象,这是需要求助于placement new()。placement new()是C++标准库的一部分,被申明在头文件中,C++标准默认实现如下:

void* operator new(std::size_t, void* __p) throw()

{

  return __p;

}

注意:

(1)placement new()的函数原型不是void* placement new(std::size_t, void* __p),placement new()只是operator new()的一个重载,多了一个已经申请好的空间,由void* __p指定;

(2)用法是new (addr) constructor(args)。默认版本的placement new()只是对addr指定的内存空间调用构造函数进行初始化,然后返回内存空间首地址,其它什么也不做。为何称为定位放置new,从其作用上可以看出用于在指定内存空间上调用类构造函数构造类对象。

给定一块已经申请好的内容空间,由指针void* ptr指定,考察如下程序。

#include <iostream>
using namespace std;

class A
{
	int num;
public:
	A()
	{
		cout<<"A's constructor"<<endl;
	}

	~A()
	{
		cout<<"A's destructor"<<endl;
	}
	
	void show()
	{
		cout<<"num:"<<num<<endl;
	}
};

int main()
{
	char mem[100];
	mem[0]='A';
	mem[1]='\0';
	mem[2]='\0';
	mem[3]='\0';
	cout<<(void*)mem<<endl;
	A* p=new (mem) A;
	cout<<p<<endl;
	p->show();
	p->~A();
}

程序运行结果:

0024F924

A's constructor

0024F924

num:65

A's destructor

阅读以上程序,注意以下几点。

(1)用定位放置new操作,既可以在栈(Stack)上生成对象,也可以在堆(Heap)上生成对象。如本例就是在栈上生成一个对象。

(2)使用语句A *p=new (mem) A;定位生成对象时,会自动调用类A的构造函数,对象生命周期结束时,也需要显示调用类的析构函数,避免内存泄漏,如本例中的p->~A()。

(3)万不得已才使用placement new(),只有当你真的在意对象在内存中的特定位置时才使用它。例如,你的硬件有一个内存映象的I/O记时器设备,并且你想放置一个Clock对象在那个位置。

总结:

(1)若想在堆上建立一个对象,应该用new运算符,它既分配内存又调用其构造函数进行初始化。

(2)若仅仅想分配内存,应该调用operator new(),它不会调用构造函数。若想定制自己在堆对象被建立时的内存分配过程,应该重写自己的operator new()。

(3)若想在一块已经获得的内存空间上建立一个对象,应该用placement new()。虽然在实际开发过程中,很少需要重写operator new(),使用内置的operator new()即可完成大部分程序所需的功能。但知道这些,有助于对C++程序内存管理的认识。

3、malloc的用法

Malloc 向系统申请分配指定size个字节的内存空间。返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。

举例

int main()

{

​    int *p = NULL;

​    int n;

​    cin >> n;

​    p = (int*)malloc(sizeof(int)*n);//不能使用p=(int*)malloc(4*n);

​    for (int i = 0; i < n;i++ )

​    {

​       p[i] = i;//*(p+i)=i;

​        

​    }

​    for (int i = 0; i < n; i++)

​    {

​       cout << *(p+i)<<""<< endl;

​    }

​     

​    free(p);//释放后p为失效指针。

​    p = NULL;//释放完 一定要将p赋成空指针。

​    return 0;

}

原型

extern void *malloc(unsigned int num_bytes);

头文件

在TC2.0中可以用malloc.h或 alloc.h (注意:alloc.h 与 malloc.h 的内容是完全一致的),而在Visual C++6.0中可以用malloc.h或者stdlib.h。

返回值

如果分配成功,则返回指向被分配内存的指针(此存储区中的初始值不确定);否则返回空指针NULL。当内存不再使用时,应使用free()函数将内存块释放。函数返回的指针一定要适当对齐,使其可以用于任何数据对象。

说明

关于该函数的原型,在旧的版本中malloc返回的是char型指针,新的ANSIC标准规定,该函数返回为void型指针,因此必要时要进行类型转换。

函数声明

void *malloc(size_t size);

工作机制

malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。

备注

1、void* 表示未确定类型的指针,void *可以指向任何类型的数据,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者…)。

2、malloc 函数返回的是 void * 类型。对于C++,如果你写成:

p = malloc (sizeof(int));

则程序无法通过编译,报错:“不能将 void* 赋值给 int * 类型变量”。所以必须通过 (int *) 来将强制转换。而对于C,没有这个要求,但为了使C程序更方便的移植到C++中来,建议养成强制转换的习惯。

3、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。如果你写成:

int* p = (int *) malloc (1);

代码也能通过编译,但事实上只分配了1个字节大小的内存空间,当你往里头存入一个整数,就会有3个字节无家可归,而直接“住进邻居家”!造成的结果是后面的内存中原有数据内容被改写。

注意点

\1. 申请了内存空间后,必须检查是否分配成功。

\2. 当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

\3. 和free()函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。

\4. 虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

5、free()释放的是指针指向的内存!注意!释放的是内存,不是指针!指针并没有被释放,指针仍然指向原来的存储空间。指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。

6、“int i = 5;”表示分配了4字节的“静态内存”。这里需要强调的是“静态内存”和“静态变量”虽然都有“静态”两个字,但是他们没有任何关系。不要以为“静态”变量的内存就是“静态内存”。静态变量的关键字是 static,他与全局变量都在“静态存储区”中分配的,这块内存在程序编译的时候就已经分配好了,而且在程序中的整个运行期间都存在,而静态内存是在栈中分配的,比如局部变量。

那么我们如何判断一个内存是静态内存还是动态内存呢?凡是动态分配的内存都有一个标志;都是用一个系统的动态内存函数来实现的,如malloc或者calloc,我们一般使用 malloc,calloc使用比较少。

那么如何用malloc动态分配内存呢?

int *p = (int *)malloc(4);

他的意思请求系统分配4字节的内存空间,并返回第一字节的地址,然后付给指针变量 p。

那么指针变量p是静态分配的还是动态分配的呢?静态分配的,前面讲过,动态分配的内存空间都有一个标志,即都是用一个系统的动态分配函数实现的,而指针变量p是用传统方式定义的,所以是静态分配的内存空间。而p所指向的内存是动态分配的。那么动态分配和静态分配到底有什么区别?

如内存什么时候分配,什么时候释放,由谁释放,怎么分配,怎么释放,哪块内存可以使用,哪块内存不能使用,哪块内存可以读写,哪块内存不能读写,这些问题形成了计算机语言的语法规则。

\#include<stdio.h>

\#include<stdlib.h>

int main(void)

{

​    while(1)

​    {

​       int *p = malloc(1000);

​    }

​    return 0;

}

上面这个小程序就是一个典型的简单的木马病毒,只要运行一会,你的计算机就死机了,死机的速度的快慢取决于malloc后面括号中数字的大小。数字越大,“死的越快”。

实质

它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足

用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。

如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。

底层实现

参考:

https://so.csdn.net/so/search/s.do?q=malloc函数底层实现&t=&u=

https://blog.csdn.net/z_ryan/article/details/79950737

https://blog.csdn.net/mmshixing/article/details/51679571

https://www.cnblogs.com/vincently/p/4842221.html

4、new和malloc的区别

(1)返回类型

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,因此new是符合类型安全性的操作符。

malloc内存分配成功返回的是void *,需要通过强制类型转换将void *指针转换为我们需要的类型。

比如:

int *p;

p = new int;

//返回类型为int* 类型(整数型指针),分配大小为 sizeof(int);

或:

int* parr;

parr = newint[100];

//返回类型为 int* 类型(整数型指针),分配大小为 sizeof(int) * 100;

而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针。

int* p;

p = (int*) malloc(sizeof(int)*128);

//分配128个(可根据实际需要替换该数值)整型存储单元,

//并将这128个连续的整型存储单元的首地址存储到指针变量p中

malloc 也可以达到 new [] 的效果,申请出一段连续的内存,方法无非是指定你所需要内存大小。

比如想分配100个int类型的空间:

int* p = (int*) malloc( sizeof(int) * 100 );

//分配可以放得下100个整数的内存空间。

另外有一点不能直接看出的区别是,malloc只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。

除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。

(2)属性

new/delete是C++的关键字,需要编译器的支持。

malloc/free是库函数,需要头文件的支持。

(3)参数

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。

malloc则需要显式地指出所需内存的大小。

(4)分配失败的结果
new内存分配失败,会抛出bac_alloc异常。
malloc分配失败则会返回NULL。

(5)自定义类型

new会先调用operator new函数,以此申请足够的内存(通常底层使用malloc来实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。

malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

(6)重载

C++允许重载new/delete操作符,布局new的就不需要为对象分配内存了,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。

malloc不允许重载。

(7)内存区域

new操作符从自由存储区(free store)上为对象动态分配内存空间。其中,自由存储区是C++基于new操作符的一个抽象概念,凡事通过new操作进行内存申请的,该内存都称为自由存储区。

malloc函数是从堆上动态分配内存的。其中,堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。C语言使用malloc从堆上分配内存,使用free释放已经分配的对应内存。

自由存储区不等于堆。

(8)数组分配

new有明确的方式处理数组的分配,即new[],释放也有delete[],malloc没有。

(9)设置内存分配器

new可以设置自己的内存分配器,malloc不可以。

(10)重新分配内存

定义类型对象构造和析构工作。

(6)重载

C++允许重载new/delete操作符,布局new的就不需要为对象分配内存了,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。

malloc不允许重载。

(7)内存区域

new操作符从自由存储区(free store)上为对象动态分配内存空间。其中,自由存储区是C++基于new操作符的一个抽象概念,凡事通过new操作进行内存申请的,该内存都称为自由存储区。

malloc函数是从堆上动态分配内存的。其中,堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。C语言使用malloc从堆上分配内存,使用free释放已经分配的对应内存。

自由存储区不等于堆。

(8)数组分配

new有明确的方式处理数组的分配,即new[],释放也有delete[],malloc没有。

(9)设置内存分配器

new可以设置自己的内存分配器,malloc不可以。

(10)重新分配内存

malloc可利用realloc重新分配内存,new不可以。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值