C++中的new和delete真的复杂吗?(上)

C++相关笔试面试中new与malloc以及delete与free是考察被面试者C++基本功的重点,也是难倒众多C++开发
人员的一个相关难点。所以很多人就说C++中new和delete很复杂,运行机制很隐秘,运行原理很神奇。但真的是
这样的吗? 今天我就为大家揭盖new与delete的神秘面纱!

如果想要了解内存对齐的秘密就来点这个链接吧!

在C++中newdelete分别是关键字成员中的一员。但大家也都称呼他们为 “运算符” 这是为什么呢?答案就在他们的运行过程中揭晓!

1.new关键字

C++ Prim Plus中如此描述new运算符:
new运算符根据类型来确定需要多少字节的内存。然后它找到这样的内存,并返回它的地址。

要想了解new运算符具体干了那些事,我们就必须分析new的具体执行过程。
  注意new的实现虽然根据不同的编译环境不同,但大体过程基本相同。

a = new int;
00361270  push        4  
00361272  call        dword ptr ds:[3630A8h]  
00361278  add         esp,4  

他要去哪呢?步入跟进我们发现原来跳转到了:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)

为什么呢?这是因为C++中提到的new,至少可能代表以下三种含义:new operatoroperator newplacement new
  new operator: 我们平时使用的new
  operator newnew operator的第一步是通过operator new完成的。这里的new就相当于一个运算符号,是可以重载的。
  因为我们调用的是new operator 所以它调用operator new来完成工作。

这个operator new函数看起来样子是好可怖,但不比担心我们看下他到底做了什么?

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
    {       // try to allocate size bytes
            //__CRTDECL 就是 __cdecl 感兴趣大家可以自行研究
    void *p;
    while ((p = malloc(size)) == 0)
            if (_callnewh(size) == 0)
            {       // report no memory
                    _THROW_NCEE(_XSTD bad_alloc, );
            }

    return (p);
    }

我们仔细观察就会发现 operator new 现是调用了malloc函数申请内存,而当申请失败也就是返回空指针时,判断 _callnewh(size) 返回值是否为0,若为0则抛出一个异常,非0则继续循环执行malloc
  _callnewh的作用就是调用一个被称作new_handler的函数,这里注意,在有些文章中说_callnewh是一个new_handler这是不正确的,_callnewh只是调用new_handler,作用类似与回调函数!
  看清operator new的内部结构后我们发现,在简单的数据类型情况下,原来这个函数的功能十分简单,就是以malloc为主体,对malloc申请失败的情况做了一下特殊的处理。
  而对于new_handler函数,在VS中我们可以使用_set_new_handler函数来设置。

#include <iostream>
#include <new.h>
using namespace std;

int  MyNewHandler(size_t size)
{    
    cout << "MyNewHandler out" << endl;
    return 1;
}
int main()
{
    _set_new_handler(MyNewHandler);
    while (true)
    {
        int* a = new int[10000000];
    }
    return 0;
}

通过以上程序可以很直观的了解new_handler的作用情况。

2.new在复杂情况的表现:

通过上面这个例子我们知道了new在简单类型下的具体执行过程,那么new在复杂类型下又是怎么执行的呢?
我们看一下下面这个例子:

class MyObject
{
public:
    int a;
    MyObject()
    {
        a = 1;
    }
};

int main()
{
    MyObject *m = new MyObject();
    return 0;
}

这是他在DEBUG模式下的汇编码:

    MyObject *m = new MyObject();
003E147D  push        4  
003E147F  call        operator new (03E1190h)  // 调用operator new
003E1484  add         esp,4  
003E1487  mov         dword ptr [ebp-0E0h],eax  
003E148D  mov         dword ptr [ebp-4],0  
003E1494  cmp         dword ptr [ebp-0E0h],0  
003E149B  je          main+70h (03E14B0h)  
003E149D  mov         ecx,dword ptr [ebp-0E0h]  
003E14A3  call        MyObject::MyObject (03E119Fh)  // 调用构造函数!
003E14A8  mov         dword ptr [ebp-0F4h],eax  
003E14AE  jmp         main+7Ah (03E14BAh)  

注意以上汇编码在Release模式下可能无法得到,因为编译器为了执行速度,在编译时会对Release代码做出特殊优化。
  通过上面的代码我们就可以很直观的看出new在复杂类型时的执行过程:先调用operator new分配空间,再调用构造函数进行初始化。

3.new的执行过程:

现在new的执行过程就很清楚了:

   new -> operator new -> malloc

这是new的基本部分,如果内存分配成功,那么operator new就会直接返回。
  而内存分配出错,也就是malloc返回指针为空:

  malloc出错 -> 调用new_handler -> 若new_handler返回为0 -> 抛出异常
             |
             v
         若new_handler返回非0 -> 继续调用malloc

若是简单类型那么new到这里基本就结束了,但要是复杂类型,new还要继续调用构造函数。
  这下我们就明白了newmalloc的区别了,new会调用malloc进行内存分配的操作。但他和malloc不用的是,他分配失败时会调用new_handler,而new_handler返回0的情况抛出异常,而malloc只会返回一个空指针。

4.重载new运算符

前面说过大家称呼new关键字为“运算符”,而我们知道在C++中运算符是可以重载的,那么是否意味着我们可以为我们自己的类定制一个new运算符呢?答案是肯定的!

class MyClass
{
public:
    MyClass()
    {
        _val = 1;
    }
    void * operator new(size_t size)
    {
        std::cout << "MyClass operator new!" << std::endl;
        return ::operator new(size);
    }
private:
    int _val;
};

int main()
{
    MyClass *m = new MyClass();
    std::cin.get();
    return 0;
}

上面的例子演示了一个重载operator new的例子。
  上面的例子中在调用全局的operator new之前,我们加入了自己的特殊处理,不过要注意返回值是 void *

5.new[]

相对于new,new[]多了一些步骤。
  简单类型new[]中,首先new[]调用了operator new[]

void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
    {   // try to allocate count bytes for an array
    return (operator new(count));
    }

operator new[]根据所需数目调用operator new
  
  复杂类型中执行完operator new[]后还会利用一个vector constructor iterator来记录new所需的构造函数的地址等信息。
  那么编译器是如何知道要new多少个元素呢?原来在new[]时编译器会在数组的头部也就是数组指针所指向的位置加上数组的长度,也就是一个四字节的_DWORD
  也正是这四个字节导致我们使用new[]创建复杂类型数组之后,无法使用delete来释放而只能使用delete[]来释放。
  想了解delete细节吗?那么我们下篇文章再来细说delete

附vector constructor iterator结构:vector_constructor_iterator_(数组首地址, 对象大小, 数组个数, 对象构造函数, 对象析构函数);(不同实现下可能会有差别)

简书●null122转载请注明出处

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容