new和malloc的区别--面试问题及其延伸

参考博客:https://blog.csdn.net/zhong29/article/details/80930919

malloc的实现方式

malloc函数将内存空间中可用的内存块连接成一个很长的空闲链表。当调用malloc函数的时候,就会在链表中进行遍历,直到找到满足用户申请空间大小的内存块,然后就从链表中把这段内存拿出来分配给用户,剩下的接回原空闲链表。如果无法获取符合大小的内存块的时候,就会放回NULL指针,因此如果用malloc申请空间就一定要进行返回值判断。
同时用malloc返回的指针是对齐的,为了能够使得void*适用于任何对象。大多数实现所分配的空间都会比申请空间来的大一些,额外的空间用来记录管理信息——分配块的长度、指向下一个分配块的指针。

当用户调用free函数的时候就把申请的内存块重新接到空闲链表上。

这里提一下realloc,realloc函数可以增减之前分配的空间的长度,如果发现当前的存储区域的高位地址上没有足够的空间了,就会分配一个新的足够大的存储区域,将现有的内容复制过去,同时释放原有存储区域的内容。这里要注意的是,因为realloc可能会移动位置,如果用指针指向该地址,就有可能使得指针无效

new的实现

先看下new的源码

_GLIBCXX_WEAK_DEFINITION void *
    operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
    {
      void *p;

      /* malloc (0) is unpredictable; avoid it.  */
      if (sz == 0)
        sz = 1;
      p = (void *) malloc (sz);
      while (p == 0)
        {
          new_handler handler = std::get_new_handler ();
          if (! handler)
            _GLIBCXX_THROW_OR_ABORT(bad_alloc());
          handler ();
          p = (void *) malloc (sz);
        }

      return p;
    }

从源码可以看出实际上new也只是申请了内存空间。这里涉及到一个new_handler,他的作用就是反复申请内存空间,如果这个new_handler做不了这个工作,那么就换新的new_handler,如果没有new_handler能做这个工作就把一个空指针传给handler,就可以实现异常的抛出。

总结下new_handler的工作:

  • Make more memory available,使得下一次内存分配能够成功,如删除其他无用的内存,使得系统有更多可用的内存。
  • Install a different new_handler,如果当前的new_handler分配不了这么多内存,或者他知道另一new_handler能完成,则可以调用set_new_handler来设置另一个new_handler
  • Deinstall the new_handler,就是如果没有能分配这么大内存的handler,就会把空指针返回。这样就可以抛出异常
  • Throw an exception,类型为bad_alloc的异常不会被operator new捕获,会被传播到内存请求的地方。

那么new的过程也可以理解了,首先调用operator new函数申请一片空间,然后改变指针类型从void* -> 某个指针类型,再进行构造函数的调用

pt=static_cast(operator new(sizeof(T)));
pt->T();

new和malloc的异同

  1. new和malloc都是在堆上开辟内存。malloc只开辟了空间,没有初始化,而new不仅开辟了空间还进行了初始化。
  2. malloc需要传入初始化的字节数,指定开辟空间的大小,而new就不用了,new的工作方式是就是上面代码中显示的,按照对象的大小获取字节数,然后开辟空间大小。
  3. malloc失败返回NULL,new失败返回的是bad_alloc的异常,因此new对象需要异常捕获来判断(其实可以不用异常捕获,但是判断是一定需要的)
  4. malloc用free来释放,new用delete来释放,new[]—delete[]
  5. malloc只有一种开辟内存的方式,而new分为普通new,nothrow的new,const new 和定位new

扩展知识1

  • nothrow new
#include<new>
Manager* pManager = new (std::nothrow) Manager();
if(NULL==pManager)
{
    return false;
}

//或者设置set_new_handle 自定义异常抛出函数
#include <new>
#include <iostream>
#include <stdlib.h>

using namespace std;

void __cdecl newhandler()
{
    return;
}

int main()
{
    set_new_handler (newhandler);

    Manager * pManager = new (std::nothrow) Manager();
    if(NULL == pManager)
    {
        //记录日志
        return false;
    }
}
  • const new

暂不整理,

了解下常量指针和指针常量的区别
常量指针:const int *p=&a; 常量指针,这是一个指针,不过指向的内容是常量。const修饰数据类型,地址可以改,但是内容不可改

int a=1,c=2;
const int *b=&a;
b=&c;//对的
*b=2;//报错

指针常量:int * const p=&a; 指针常量,这是一个常量,就是指针本身是常量。因此地址不可以改,但是内容可以改。

int a=1,c=2;
int * const b=&a;
b=&c;//错
*b=2;//对
  • placement new
    就是在指定的位置上来实现对象。
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

从源码来看placement new没有申请新的空间,直接返回了第二个参数的指针。
那么placement new的过程应该是

T p=new(alreadyExistPoint) T()

//分解步骤如下
void* p1=::operator new(sizeof(T),alreadyExistPoint);
T* p=static_cast<T*> p1;

扩展知识2

存储空间的布局

C程序一直由几个部分组成

  • 正文段。这里是CPU执行的机器指令部分。通常正文段是可共享的,只读的。
  • 初始化数据段。 包含了程序中需明确的赋予初值的变量,例如除了函数外的声明 int maxcount=999;
  • 未初始化数据段。 在程序开始执行之前,内核将此段的数据赋予初值或者空指针。
  • 栈。 自动变量和每次函数调用时所需要保存的信息都存放在此段中。每次函数调用时,其返回地址以及调用者的环境信息都存放在栈中。 对于递归函数,每次自身被调用时,就会产生一个栈帧,因此函数之间的变量集就不会互相干扰。
  • 堆。 通常在堆中进行动态存储的分配。
    以上摘自UNIX环境高级编程

从上面的描述中我们可以知道栈是由编译器自动分配和释放的。而堆是由程序员来操作的。

如何只在堆上或者只在栈上创建对象呢。

简单说下,只在堆上就是只能通过new的方式来动态分配空间,因此就要想办法禁用在编译期间就创建对象的方式如 A a(args); 禁用的办法就是讲构造析构函数设为protected函数,那么编译器就不会在编译期间开辟栈空间给他。

class A{
protected:
	A(){};
	~A(){};
public:
	static A* create(){
		return new A();
	}	
	void destory(){
		delete this;//直接删除传入的this对象,成员函数能调用protected对象
	}
};

接下来就是只在栈上生成对象,很简单就把operator new和operator delete禁用掉即可

class A{
private:
	void* operator new(size_t){};
	void operator delete(void* ptr){};
public:
	A(){};
	~A(){};
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章