VC 运行时库中的 new/delete 使用

目录


缘起  

我用 dependency walker(简称 depends)跟了一下,发现 operator new/delete 函数是从 msvcr[ver].dll 中导出的(如图),其中 ver 是 VC 运行时库(CRT)的版本,例如:VC 2005(简称 VC8)环境下,Release 版本为 80,Debug 版本为 80d。本以为 operator new/delete 是从另一个 msvcp[ver].dll 导出的,其实不是,msvcp[ver].dll 有自己导出的 operator new/delete,但并不是我们编程常规用的 new/delete 操作符。

 

 

CRT 的动态链接模块  

VC 的运行时库,通常简称 CRT。特指时,它表示 msvcr[ver].dll 这个动态链接库,但在泛义上它和其它几个动态链接库有着紧密的联系,概念上的划分也有共享的部分。这几个都含 Runtime Library 语义的 dll 分别是:msvcr[ver].dll、msvcp[ver].dll、msvcm[ver].dll 和 msvcrt.dll。

看看这些 dll 名字的都代表了什么意思:

  • msvcr[ver].dll

    全称为 Microsoft C Runtime Library。msvcr[ver].dll 导出所有的标准 C 库 API,和微软的标准 C 库扩展 API,以及一些 C++ 基本语言特性需要的 API。

  • msvcp[ver].dll

    全称 Microsoft C++ Runtime Library。msvcp[ver].dll 导出标准 C++ 库中的 STL 和 iostream 类。

  • msvcm[ver].dll

    它也叫做 Microsoft C Runtime Library,不过是给托管代码用的 CRT,所以后缀 m 的含义可以理解为 managed(托管)。msvcm 没有任何对应的静态库,也就是说要使用托管 CRT,只能动态链接到 msvcm[ver].dll。有两种方式指定使用托管 CRT:

    1. 使用 /clr 编译选项,这时客户程序可以使用 managed/native 混合代码,并且使用 msvcmrt.lib 导入库。

    2. 使用 /clr:pure 编译选项,这时客户程序使用纯粹的 MSIL 托管代码。

    由于 CLR 的 COM 实质,msvcm[ver].dll 依赖这些 dll:Native CRT:msvcr[ver].dll,OLE Library:ole32.dll,以及 .NET Runtime Execution Engine:mscoree.dll。

上面的 msvcr、msvcp、msvcm 是 VC 引入的 dll,在部署应用程序时可以使用微软提供的 VC Redistributable Package 包来安装这些 dll,安装使用 side by side 方式,dll 拷贝到 %SystemRoot%/winsxs 目录下,并且这些 dll 都是以 Release 方式编译的。

另外,还有一种直接拷贝这些 dll 到目标系统的部署方法,参考 MSDN 的 How to: Deploy using XCopy (VC8) 的 Deploying Visual C++ library DLLs as private assemblies 章节。这些要拷贝的 dll 保存在:Path-to-VS/VC/redist(Release 版)和 Path-to-VS/VC/redist/Debug_NonRedist(Debug 版)。

  • msvcrt.dll

    全称 Windows NT CRT DLL,它的名字是固定不带版本号的,位置总在 %SystemRoot%/system32 下。msvcr[ver].dll 和 msvcrt.dll 的作用区别借用 MSDN 的话说为:

    from: C Run-Time Libraries (VC8)

    What is the difference between msvcrt.dll and msvcr80.dll?

    The msvcrt.dll is now a "known DLL", meaning that it is a system component owned and built by Windows. It is intended for future use only by system-level components.

    msvcr[ver].dll 依赖 msvcrt.dll。

使用 msvcr[ver].dll 导出的 new  

下面说说我们用 VC 编译的 C++ 程序中,常规使用的 new/delete 操作符,也就是 msvcr[ver].dll 导出的 operator new/delete 函数,它们的调用和二进制模块 msvcr[ver].dll 的导出函数的对应关系,以及申请内存失败的处理机制。

声明:下面程序的编译、测试环境均为 VC8,用模块名 msvcr80[d].dll 表示无差别情况下的 Debug 或 Release 版 CRT 动态链接库;当提到 VC 的头文件、源文件、静态库、导入库、对象文件等路径时,均是相对于 Visual Studio 2005 的安装目录;给出的 MSDN 参考,若非指明,也是适用于 VC8 版本。

工具:用 depends 查找模块导出符号时,有些不方便。可以使用 VC 的附带工具 dumpbin,用法参考 DUMPBIN Reference (VC9),例如:dumpbin /exports msvcp80d.dll > res.txt。

头文件和模块 msvcr80[d].dll 中 new/delete 的对应  

有两个关于 new/delete 的头文件:标准 C++ 库的头文件 <new>(没有 .h 后缀),和 CRT 的头文件 <new.h>。<new.h> 对应的模块是 msvcr80[d].dll;而 <new> 中声明的大部分函数对应于 msvcr80[d].dll,如常规的 new/delete 函数,而有些函数,例如下面会讲的 set_new_handler(),则对应 msvcp80[d].dll。

msvcr80[d].dll 导出的 new/delete 有:

1// 这是模块 msvcr80[d].dll 导出的函数/二进制接口
2void* operator new(unsigned int);
3void* operator new(unsigned int, int, char const*, int);
4void* operator new[](unsigned int);
5void* operator new[](unsigned int, int, char const*, int);
6  
7void operator delete(void*);
8void operator delete[](void*);

这些就是我们编程中常规用的 new/delete 的二进制接口。

在 C++ 源码级别,总共有 6 个 new 函数声明,上面的第一个导出函数 void* operator new(unsigned int) 对应其中的 4 个:

1// 这是源文件中声明的函数原型
2void* operator new(size_t count) throw(std::bad_alloc);
3void* operator new(size_t count, const std::nothrow_t&) throw();
4  
5void* operator new[](size_t count) throw(std::bad_alloc);
6void* operator new[](size_t count, const std::nothrow_t&) throw();

为什么二进制的 void* operator new(unsigned int) 也会对应声明的 operator new[] 原型?后面会慢慢道来。

另外 2 个 new 函数的声明是:

1void* operator new(size_t count, void* object) throw();
2void* operator new[](size_t count, void* object) throw();

这两个被称为放置式的 new,不对应任何运行时库的动态链接库,它们的二进制代码会编译进客户程序中。

上面 6 个 new 的使用语法参考:operator new (CRT)operator new[] (CRT)

下面就说说这几个 new 的区别。

 

标量 new 与矢量 new[]  

标量 new(scalar new),即 operator new;矢量 new(vector new),operator new[]。

按照 MSDN operator new[] (CRT) 的说法,当使用 new 申请数组块内存时,就调用矢量的 operator new[]。

我在实际的 VC8 环境测试中,发现下列客户代码均会调用 msvcr80[d].dll 导出的 void* operator new(unsigned int),而非导出的 void* operator new[](unsigned int):

1// 基本类型
2char* s1 = new char[10];
3int* s2 = new int[10];
4// POD 的平坦结构
5TestStruct* s3 = new TestStruct[10];
6// non-POD 的类
7TestObj* s4 = new TestObj[10];

反汇编调试后发现,在客户程序中 VC8 编译器已经将申请数组块的整体大小计算出来了,比如 sizeof(TestObj) = 40,则编译器就会计算出 new TestObj[10] 申请的大小为 40 * 10 = 400。

  • 如果此时仅包含 <new.h>,或者不包含 <new> 和 <new.h> 任一个,则会调用 msvcr80[d].dll 中导出的 void * operator new(unsigned int)。

  • 如果仅包含 <new>,或者 <new>、<new.h> 两个都包含(顺序无关),则会调用 VC/crt/src/newaop.cpp 中定义的 operator new[](aop 的含义是 array operator)。那么 newaop.cpp 中的 operator new[] 是否是 msvcr80[d].dll 中导出的 void* operator new[](unsigned int)?答案不是,从 VC 的 Call Stack 中看出 newaop.cpp 对应的二进制模块是客户程序的模块而非 msvcr80[d].dll。newaop.cpp 定义的 operator new[] 是 msvcr80[d].dll 导出的 void* operator new(unsigned int) 的简单包裹,最后将调用传给它。

delete/delete[] 的调用也有这种受包含 <new> 还是 <new.h> 影响的问题,见下面对 delete 的讨论。

所以问题是,虽然在 msvcr80[d].dll 中导出了 operator new[],但似乎不能通过标准的方法使用它。

放置式 new  

按 new 的行为是申请存储位置,还是将对象放置到某个存储位置,可以分为:非放置式(nonplacement)new,和 放置式(placement)new。

放置式 new 语法参考:《C++ 程序设计语言》特别版(Bjarne)章节 10.4.11 对象的放置。有两种惯用的放置手法:1. 放置到已有存储位置。2. 使用 Arena(场地)分配存储位置(自定义放置式 new)。

VC CRT 中提供了第一种放置式 new,在 <new> 和 <new.h> 中都有声明:

1// <new.h> 中声明:
2inline void *__CRTDECL operator new(size_t, void *_Where);
3  
4// <new> 中声明:
5inline void *__CRTDECL operator new(size_t, void *_Where) _THROW0();
6inline void *__CRTDECL operator new[](size_t, void *_Where) _THROW0();

这些放置 new 都是 inline 函数,声明的同时给出定义,函数体都是一句简单的 return (_Where),所以不存在 msvcr80[d].dll 中导出的放置式 new。

测试放置式 new 的代码:

01#include <new.h>
02  
03class TestObj
04{
05public:
06    int a[10];
07    TestObj(int _a = 1)
08    {
09        for (int i = 0; i < 10; i++)
10            a[i] = _a;
11    }
12};
13  
14int main()
15{
16    TestObj* place = (TestObj*) malloc(sizeof(TestObj));
17  
18    // 用 TestObj(2) 初始化放置位置,注意:这种初始化是直接的,即
19    // 调用直接构造函数 TestObj() 完成,而没有拷贝构造 TestObj(const TestObj& obj)
20    // 或赋值 TestObj& operator=(const TestObj& obj) 语义。
21  
22    TestObj* s1 = new(place) TestObj(2);
23  
24    // s1 和 place 指向同一块分配内存,下句会将这块内存释放
25    delete s1;
26    return 0;
27}

no-throw 的 new  

还有一种 MSDN 上称为 placement, no-throw 的 operator new/new[],在 <new.h> 和 <new> 中都有声明:

1// <new.h> 中的声明:
2__bcount_opt(_Size) void *__CRTDECL operator new(size_t _Size, const std::nothrow_t&) throw();
3__bcount_opt(_Size) void *__CRTDECL operator new[](size_t _Size, const std::nothrow_t&) throw();
4  
5// <new> 中的声明:
6__bcount_opt(_Size) void *__CRTDECL operator new(size_t _Size, const std::nothrow_t&) _THROW0();
7__bcount_opt(_Size) void *__CRTDECL operator new[](size_t _Size, const std::nothrow_t&) _THROW0();

不过它和放置位置没有关系,而是影响内存申请失败时的报告机制,如果使用这种 new,则申请失败时将以 operator new 返回 0 值表示失败,否则使用默认抛出异常的方式表示申请失败。

以调用 new(std::nothrow) char[BIG_SIZE] 为例,调用顺序如下:

  1. 客户程序模块: newaopnt.cpp: void* __CRTDECL operator new[](::size_t count, const std::nothrow_t& x)

  2. 客户程序模块: newopnt.cpp: void* __CRTDECL operator new(size_t count, const std::nothrow_t&)

  3. msvcr80[d].dll: 导出的 void* operator new(unsigned int)

newaopnt.cpp 和 newopnt.cpp 在 VC/crt/src 目录下,后缀 nt 表示 no-throw。

更详细的 new 申请失败报告机制在后面叙述。

调试版的 new  

在 msvcr80[d].dll 中有两个 4 个参数的 operator new 函数的导出:

1void* operator new(unsigned int, int, char const*, int);
2void* operator new[](unsigned int, int, char const*, int);

这两个函数的声明在 <crtdbg.h> 中,实现源码在 <dbgnew.cpp>,用法参考 The Debug Heap from C++。示例:

在工程公共头文件中:

01// common.h
02  
03#include <crtdbg.h>
04  
05// Defines global operator new to allocate from client blocks
06  
07#ifdef _DEBUG
08    #define DEBUG_CLIENTBLOCK   new(_CLIENT_BLOCK, __FILE__, __LINE__)
09#else
10    #define DEBUG_CLIENTBLOCK
11#endif // _DEBUG

在源文件中:

01#include "common.h"
02  
03#ifdef _DEBUG
04#define new DEBUG_CLIENTBLOCK
05#endif
06  
07int main()
08{
09    char* p1;
10    p1 = new char[40];
11    _CrtMemDumpAllObjectsSince(NULL);
12}

上面申请数组块内存时,会调用 msvcr80d.dll 导出的 void* operator new[](unsigned int, int, char const*, int)。

_CrtMemDumpAllObjectsSince(NULL) 将从程序开始到其调用点的所有堆对象调试信息,转储到调试输出,比如上面用 operator new[](unsigned int, int, char const*, int) 申请内存时,就会产生调试信息。_CrtMemDumpAllObjectsSince() 只在调试版起作用(有 _DEBUG 定义),当没有 _DEBUG 定义时,_CrtMemDumpAllObjectsSince() 就被替换成一个空操作 ((void)0),如同 _ASSERT() 的实现一样(_ASSERT() 也在 <crtdbg.h> 中定义)。可以用 VC 的调试 Output 窗口,或 DebugView 工具查看 _CrtMemDumpAllObjectsSince() 的输出。

 

new 申请内存失败  

参考:The new and delete Operators

msvcr80[d].dll 中导出的 new 的报告申请失败的默认方式是抛出 std::bad_alloc 异常。测试例子:

01#include <stdio.h>
02#include <typeinfo>
03#include <new>
04  
05#define BIG_SIZE    0x7fffffff
06  
07char* s1 = NULL;
08  
09try
10{
11    s1 = new char[BIG_SIZE];
12}
13catch (std::bad_alloc& e)
14{
15    printf("Caught: %s/n", e.what());
16    printf("Type: %s/n", typeid(e).name());
17}

上面的代码编译后不会链接到 msvcp80[d].dll,因为标准 C++ 异常类(即 std::bad_alloc)和 C++ RTTI 类(type_info),均在 msvcr80[d].dll 中有导出。

如果想使用返回 0 来表示 new 申请内存失败,除了使用上面提到的 placement, no-throw 的 new 外,调试版的 operator new/new[](unsigned int, int, char const*, int) 也是以返回 0 表示失败,并不抛出异常。

另外,还有一种用返回 0 来表示失败的方法,就是和 VC/lib/nothrownew.obj 链接,此时便不会调用 msvcr80[d].dll 中导出的 new(用 depends 可以观察到),而调用 nothrownew.obj 中包含的 new。

VC 编译出代码的异常处理方式(Exception Handling Model)和编译选项 /EH 有关系,它会影响 C 和 C++ 两种语言中的异常处理,以及标准 C++ 规范中的异常(try-catch 结构)和 Windows 特有的异常处理方式 SEH (Structured Exception Handling)(__try-__except-__finally 结构)。

关于 VC 中 C/C++ 的异常处理,和更多 new/delete 操作的内容,请查阅文章最后的参考。

std::set_new_handler 和 _set_new_handler  返回目录

  • std::set_new_handler

    参考:

    • set_new_handler:set_new_handler 的用法。

    • operator new (<new>):包含 operator new() 的循环尝试申请工作机制,和自定义 new_handler 应该完成的功能。

    set_new_handler() 是 C++ 标准中定义的 new 申请失败处理设定函数,使用的失败处理函数类型为:typedef void (*new_handler)()。相关函数、类型在 <new> 中声明,set_new_handler() 在 msvcp80[d].dll 中导出。

    例子:

    01#include <new>
    02#include <typeinfo>
    03  
    04using namespace std;
    05  
    06#define BIG_SIZE    0x7fffffff
    07  
    08void my_handler()
    09{
    10    printf("allocation failed.");
    11  
    12    // 除非你产生更多可用的内存,否则应该抛出一个异常
    13    throw bad_alloc();
    14}
    15  
    16int main()
    17{
    18    new_handler old_handler = set_new_handler(my_handler);
    19    char* s1 = NULL;
    20  
    21    try
    22    {
    23        s1 = new char[BIG_SIZE];
    24    }
    25    catch (exception& e)
    26    {
    27        printf("Caught: %s/n", e.what());
    28        printf("Type: %s/n", typeid(e).name());
    29    }
    30  
    31    delete[] s1;
    32    s1 = NULL;
    33  
    34    set_new_handler(old_handler);
    35  
    36    return 0;
    37}

    上面代码会链接到 msvcp80[d].dll。

    自定义的错误处理函数 new_handler,必需完成 3 种功能之一:

    1. 产生更多可用的内存以供申请,例如使用垃圾回收、不常用对象交换到文件等手段,具体方式和应用有关,此时该函数可以用直接返回的方式离开,随后控制返回给 operator new(),并再次尝试申请内存。

    2. 调用 abort 或 exit 函数,让 CRT 负责并终止程序执行。

    3. 抛出一个异常,通常是 std::bad_alloc,该异常会穿过 operator new() 一直上抛到客户程序,此时 new_handler 通过异常方式离开 operator new()。

    所以用户的 new_handler 实现中,如果即没有产生更多可用内存的工作,又不通过异常或 abort/exit 方式离开 operator new(),则 operator new() 就会陷入一直调用 new_handler 的死循环。

  • _set_new_handler

    参考:_set_new_handler

    _set_new_handler() 是 CRT 提供的 new 申请失败处理设定函数,使用的失败处理函数类型为:typedef int (*_PNH)(size_t)。相关函数、类型在 <new.h> 中声明,_set_new_handler() 在 msvcr80[d].dll 中导出。

    例子:

    01#include <new.h>
    02#include <typeinfo>
    03  
    04using namespace std;
    05  
    06#define BIG_SIZE    0x7fffffff
    07  
    08int my_handler(size_t sz)
    09{
    10    printf("allocation failed, request size: %d/n", sz);
    11  
    12    // 除非你产生更多可用的内存,否则应该返回 0
    13    return 0;
    14}
    15  
    16int main()
    17{
    18    _PNH old_handler = _set_new_handler(my_handler);
    19    char* s1 = NULL;
    20  
    21    try
    22    {
    23        s1 = new char[BIG_SIZE];
    24    }
    25    catch (exception& e)
    26    {
    27        printf("Caught: %s/n", e.what());
    28        printf("Type: %s/n", typeid(e).name());
    29    }
    30  
    31    delete[] s1;
    32    s1 = NULL;
    33  
    34    _set_new_handler(old_handler);
    35  
    36    return 0;
    37}

    上面代码会仅会链接到 msvcr80[d].dll,而不会链接 msvcp80[d].dll。

    和标准 C++ 库中的 new 失败处理函数不同,CRT 中规定的失败处理函数 int (*_PNH)(size_t),有返回值和参数。_PNH 的参数 size_t,表示请求申请但失败的内存大小,而 int 型返回值表示:

    1. 返回非 0 值,表示 _PNH 做过一些产生更多可用内存的工作,控制返回给 operator new() 后,会再次尝试申请内存。该情况和 C++ new_handler 的直接返回类似。

    2. 返回 0,表示无需让 operator new() 再次尝试申请内存,申请操作已经彻底失败,最终控制会以默认抛出异常方式回到客户程序。该情况和 C++ new_handler 的以抛出异常方式结束申请类似。

    所以类似标准 C++ 库的 new_handler,_PNH 如果即没有产生更多可用内存的工作,又返回了非 0 值,则 operator new() 就会陷入一直调用 _PNH 的死循环。

    _PNH 是作用于 CRT 提供的全局 operator new() 的,要想 _PNH 也作用于 malloc(),使得 malloc() 申请失败时也调用 _PNH 处理,可以调用 _set_new_mode(1) 激活 malloc() 的失败处理机制。

    在二进制层次,_PNH 函数是在所有模块间共享的,在 dll 中设定的 _PNH 会影响到主 exe 和其它 dll 中的 operator new() 行为。

使用 msvcr[ver].dll 导出的 delete  

delete 和 delete[]  

在客户代码中使用 delete[] 时,如果仅包含 <new>,或者 <new>、<new.h> 两个都包含(顺序无关),则调用 msvcr80[d].dll 导出的 void operator delete[](void*)。

如果仅包含 <new.h>,或者不包含 <new> 和 <new.h> 任一个,则调用 msvcr80[d].dll 导出的 void operator delete(void*)。

实际上 msvcr80[d].dll 的 void operator delete[](void*) 实现,仅仅是对 void operator delete(void*) 做了一个简单的包裹。delete[] 的源码在 VC/crt/src/delete2.cpp,Release 版的 delete 的源码在 VC/crt/src/delete.cpp,Debug 版的 delete 的源码在 VC/crt/src/dbgdel.cpp。

两次重复 delete  

示例:

1char* s1 = new char[10];
2delete[] s1;
3delete[] s1;

在 Debug 配置下,即链接到 msvcr80d.dll,dbgdel.cpp 中定义的 delete 在程序运行时会检查到这种情况,并报 assert 诊断错误。

在 Release 配置下,即链接到 msvcr80.dll,如果程序运行时 attach 到 VC 的调试器,则在重复 delete 的位置会给出警告,中止运行并报错 "This may be due to a corruption of the heap, and indicates a bug in [program-name.exe] or any of the DLLs it has loaded.",可以让调试器继续运行程序。如果没有 attach 到调试器,而是独立运行程序,则不会看到任何警告提示,程序运行直到结束,这可能会造成复杂程序中 bug 的潜藏点。

delete 空指针  

在上面重复 delete 的代码中加上一句,如下:

1char* s1 = new char[10];
2delete[] s1;
3s1 = NULL;
4delete[] s1;

和重复 delete 不同,上述 delete 空指针无论是从 C++ 语法标准的角度(参考《C++ 程序设计语言》章节 6.2.6 自由存储),还是在实际的 VC8 环境中都是正确的,Debug 和 Release 版的 CRT 库均会正常运行,不会报出错误或警告。

调试版的 delete  

参考:The Debug Heap from C++

和使用调试版的 void* operator new(unsigned int, int, char const*, int) 不同,不需用户写 delete 操作的替换宏和更改任何代码,只需用 Debug 配置方式编译程序即可使用调试版的 delete,而换到 Release 方式编译就可使用一般的 delete。

msvcp80[d].dll 导出的 new/delete  

最后,不妨看看 msvcp80.dll 导出的 operator new/delete 函数,大家想想这些接口在何种情况下被调用呢?

1void* operator new(unsigned int, struct std::_DebugHeapTag_t const &, char*, int);
2void* operator new[](unsigned int, struct std::_DebugHeapTag_t const &, char*, int);
3  
4void operator delete(void *, struct std::_DebugHeapTag_t const &, char*, int);
5void operator delete[](void *, struct std::_DebugHeapTag_t const &, char*, int);

除了上述全局的 new/delete 外,msvcp80[d].dll 中还导出了 std::locale::facet 类定义的 new/delete,不过这些跟用户常规的 new/delete 操作都没有关系。

总结  

CRT 的模块 msvcr80[d].dll 中并非只含 C API,下面标准 C++ 库的 API 也在其中:

  • 常规使用的 operator new/delete,包括调试版 void* operator new(unsigned int, int, char const*, int),对应头文件 <new> 和 <new.h>。

  • 标准 C++ 异常类,对应头文件 <exception>。

  • 标准 C++ RTTI 类,对应头文件 <type_info> 和 <type_info.h>

而 msvcp80[d].dll 则侧重于标准 C++ 库中的 STL 和 iostream 类的实现,basic_string、vector、basic_ostream 等均在这里导出,另外还包括 <new> 中声明的 std::set_new_handler()。

VC 的这种混合 C API 和标准 C++ 库的模块管理方式,让人感觉很混乱并摸不着头脑。其实,微软这么做是有考虑的,试想一下,如果你即想使用 C++ 的基本语言特性,如 new/delete、RTTI 等,又不想依赖额外的标准 C++ 库,如一大堆 STL 模板类和 iostream 类,此时就可以仅链接 msvcr80[d].dll。总之,只要记住 msvcr80[d].dll 包括所有 VC 基本的 C/C++ 语言支持就好了,这也是 CRT“运行时”库的内涵所在。

参考  
  • C Run-Time Libraries:说明 VC 中:运行时库(CRT)、托管代码运行时库、标准 C++ 库,对应的静态、动态、导入库文件,以及编译选项。

  • CRT Debugging Techniques:CRT 中的调试技术,包括调试用到的宏、函数,调试版的堆管理函数 malloc、new/delete 等。

  • /EH (Exception Handling Model):VC 用来控制异常处理方式的编译选项 /EH,默认的 VC 工程通常使用 /EHsc 选项编译。

  • Exception Handling in Visual C++:讲述 VC 支持的两种异常处理方式:C++ 异常 和 SEH (Structured Exception Handling) 的工作机制和语法。建议先看其中的 Exception Specifications,明白函数的 throw() 修饰词怎样和编译选项 /EH 相互作用。

转载或改编时,请务必以链接形式注明文章 原始出处、作者信息 和 著作声明:

文章标题:VC 运行时库中的 new/delete 使用

原文作者:breaker

著作声明:原创 分类文章,采用 知识共享(CC) 署名-非商业性使用-相同方式共享 (by-nc-sa) 2.5 中国大陆 许可协议

文章地址:http://codingdao.com/wp/post/vc-crt-new-delete-usage/

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值