C++的new和delete运算符与内存分配函数和释放函数

我曾经碰到下面一段代码:

int *p = new (sizeof(int));

编译通不过,提示说是new运算符语法错误。但若换成如下:

int *p = operator new (sizeof(int));

编译顺利通过。一时不得其解。前一句代码显然是调用new运算符;后一句看起来也像是调用new运算符,毕竟其它运算符可以这样调用。

在仔细查看了C++标准后,我才发现这两句代码实际上是不一样的。前一句是调用new运算符,但后一句是直接调用内存分配函数。本文就是要澄清这两者之间的区别。

new运算符有两步操作:

  • 调用适当的内存分配函数分配内存opeator newoperator new[]
  • 调用适当的构造函数。

同样地,delete运算符也有两步操作:

  • 调用适当的析构函数;
  • 调用适当的内存释放函数operator deleteoperator delete[].

C++标准库在头文件<new>中提供了一些默认的内存分配函数和释放函数,包括会抛出异常的和不抛出异常的版本(见[ISO C++ 1998]的18.4)。严格地来讲,newdelete运算符是不能被重载的,重载的只是内存分配函数和释放函数。因此,用户可以自定义内存分配函数和释放函数。注意,内存分配函数和释放函数必须是全局函数(静态或非静态的)或类的成员函数(3.7.3.1, [ISO C++ 1998])。但是,用户不能自定义全局的placement forms的内存分配和释放函数(18.4.1.3 [ISO C++ 1998])。

new和delete运算符及对应的内存分配和释放函数

对于每一个newdelete运算符,编译器都将调用相应的内存管理函数。对于用户自定义类型,若用户以类成员函数的形式提供了内存管理函数,则编译器就调用类成员函数,否则编译器调用全局函数。对于定义在类里的成员内存管理函数,不管是否有static关键字声明都是静态成员函数(12.5 [ISO C++ 1998])。

对于new运算符,调用的内存分配函数的第一个参数是要分配内存的大小。若是放置形式(placement forms)的new运算符,new运算符的放置部分将作为内存分配函数的第二个及后续参数。

T *p = new T;        // call operator new(sizeof(T))
T *p = new T[5];     // call operator new[](sizeof(T)+delta)
T *p = new (2, f) T; // call operator new(sizeof(T), 2, f

delete运算符传给内存释放函数的第一个参数是将要释放的内存指针。若调用的内存释放函数是类的成员函数且是两参数风格(有两个参数,第一个参数是指针,第二个参数是内存大小),则内存的大小作为第二个参数(clause 5 of 12.5, [ISO C++ 1998])。delete运算符没有放置形式。

 

delete p;    // call operator delete(p) or operator delete(p, sizeof(*p))
delete[] p;  // call operator delete[](p) or operator delete[](p, sizeof(*p)*s+delta)

 

若释放的对象不是数组,则delete的操作数的动态类型与静态类型要么一致,要么其析构函数是虚拟的;若释放的对象是数组,则delete[]操作数的动态类型有静态类型必须一致。(5.3.5, [ISO C++ 1998])

内存释放函数虽然是静态的,不能动态绑定,但是若类T的析构函数是虚拟的则编译器将根据指针T *p的动态类型来决定调用合适的内存释放函数(clause 7 of 12.5, [ISO C++ 1998])。例如:

struct B {
    virtual ~B();
    void operator delete(void *);
};
struct D : B {
    void operator delete(void *);
};
void f()
{
    B *bp = new D;
    delete bp;         // use D::operator delete(void *), not B::operator delete(void *)
}

 

new运算符在初始化对象时抛出异常并且存在与operator new匹配的内存释放函数,那么这个内存释放函数将被调用以释放内存,异常在new表达式的环境下继续传播。delete运算符没有放置形式,但是存在放置形式的内存释放函数。非放置形式的内存释放函数与非放置形式的内存分配函数是匹配的;放置形式的内存释放函数与放置形式的内存分配函数,若它们的第二个及后续参数一致,则是匹配的。

class A {
public:
    A() {
        thrown exception("");
    }
    void operator delete(void *p, size_t s) {
        cout << "A::operator delete/n";
    }
};
  
int main()
{
    try {
        A *p = new A;         // 抛出异常!调用A::operator delete()
    } catch (exception) {

    }
    return 0;
}

由于A的构造函数会抛出异常,而且A定义了一个与operator new(size_t)匹配的内存释放函数,因此编译器将调用那个内存释放函数然后抛出异常。

References

  • [ISO C++ 1998] ISO C++ Standard, 1998.

Updated: 2011-04-03 16:38

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值