[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1 xcode4.2]
Q: new操作符构造一个对象,它内部究竟调用了什么?
A: 如下代码:
#include <iostream>
using namespace std;
#define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl;
class A
{
public:
A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
int value_one() const { return _value_one; }
int value_two() const { return _value_two; }
virtual void show() const { std::cout << "show A..." << std::endl; }
private:
int _value_one;
int _value_two;
};
int main (int argc, const char * argv[])
{
A *a = new A(1, 2);
return 0;
}
为了便于查看汇编,先不写delete释放代码了。main函数的汇编代码如下:
0x00001b50 <main+0>: push %ebp
0x00001b51 <main+1>: mov %esp,%ebp
0x00001b53 <main+3>: sub $0x28,%esp
0x00001b56 <main+6>: mov 0xc(%ebp),%eax
0x00001b59 <main+9>: mov 0x8(%ebp),%ecx
0x00001b5c <main+12>: mov %ecx,-0x4(%ebp)
0x00001b5f <main+15>: mov %eax,-0x8(%ebp)
0x00001b62 <main+18>: movl $0xc,(%esp)
0x00001b69 <main+25>: call 0x1dd4 <dyld_stub__Znwm>
0x00001b6e <main+30>: mov %eax,-0x14(%ebp)
0x00001b71 <main+33>: mov -0x14(%ebp),%eax
0x00001b74 <main+36>: mov %eax,(%esp)
0x00001b77 <main+39>: movl $0x1,0x4(%esp)
0x00001b7f <main+47>: movl $0x2,0x8(%esp)
0x00001b87 <main+55>: call 0x1c68 <_ZN1AC1Eii>
0x00001b8c <main+60>: mov -0x14(%ebp),%eax
0x00001b8f <main+63>: mov %eax,-0x18(%ebp)
0x00001b92 <main+66>: movl $0x0,-0x10(%ebp)
0x00001b99 <main+73>: mov -0x10(%ebp),%eax
0x00001b9c <main+76>: mov %eax,-0xc(%ebp)
0x00001b9f <main+79>: mov -0xc(%ebp),%eax
0x00001ba2 <main+82>: add $0x28,%esp
0x00001ba5 <main+85>: pop %ebp
0x00001ba6 <main+86>: ret
对于第一个call, 前面传入的参数0xc, 很可能是类A的大小;第一个call具体调用的是__Znwm例程(它在动态库中)。使用otool命令得到上面生成的可执行文件(假设为testForCpp)依赖的库名称:
使用idaq工具打开/usr/lib/libstdc++.6.dylib文件,寻找operator new函数的位置。
可以看得出来,它和call指令对应的__Znwm是对应的。
上面汇编代码的第二个call指令,正是类A的构造函数。这个可以在类A的构造函数处加断点,进入后,反汇编得到此函数的内部名称,和call对应的名称是一致的。
同时,对于operator new的内部,可以发现它会调用malloc函数,如果失败,便会抛出异常。
Q: 这么说来,上面的代码中调用的new可以分为两步来做?
A: 是的。如下代码:
int main (int argc, const char * argv[])
{
void *mem = operator new(sizeof(A)); // alloc a mem
A *a = new (mem) A(1, 2); // call construct function in mem
delete a;
return 0;
}
第一步调用operator new申请空间,第二部在第一步申请的空间上调用构造函数。
Q: operator new函数是否可以重载?
A: 是的。
void * operator new(size_t size)
{
std::cout << "operator new is called..." << std::endl;
return malloc(size);
}
int main (int argc, const char * argv[])
{
void *mem = operator new(sizeof(A)); // alloc a mem
A *a = new (mem) A(1, 2); // call construct function in mem
delete a;
return 0;
}
执行结果:
operator new is called...
不过这样要小心,因为这改变了调用new操作符的默认行为;同时,也应该提供一个重载的函数operator delete.
Q: 可以在类中重载new操作符吗?
A: 是的。
#include <iostream>
using namespace std;
#define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl;
class A
{
public:
A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
int value_one() const { return _value_one; }
int value_two() const { return _value_two; }
virtual void show() const { std::cout << "show A..." << std::endl; }
void * operator new(size_t size)
{
std::cout << "operator new is called..." << std::endl;
return malloc(size);
}
private:
int _value_one;
int _value_two;
};
class B
{
};
int main (int argc, const char * argv[])
{
A *a = new A(1, 2);
delete a;
B *b = new B;
delete b;
return 0;
}
运行结果:
operator new is called...
可以看到,类A内部重载new的函数,类B是不会调用它的。
Q: A *a = new A(1, 2); 和 A a1(1, 2); 这两句代码有什么不同呢?
A: 如下代码:
int main (int argc, const char * argv[])
{
A *a = new A(1, 2);
A a1(1, 2);
delete a;
return 0;
}
它对应的汇编代码:
0x00001a70 <main+0>: push %ebp
0x00001a71 <main+1>: mov %esp,%ebp
0x00001a73 <main+3>: sub $0x38,%esp
0x00001a76 <main+6>: mov 0xc(%ebp),%eax
0x00001a79 <main+9>: mov 0x8(%ebp),%ecx
0x00001a7c <main+12>: mov %ecx,-0x4(%ebp)
0x00001a7f <main+15>: mov %eax,-0x8(%ebp)
0x00001a82 <main+18>: movl $0xc,(%esp)
0x00001a89 <main+25>: call 0x1c50 <_ZN1AnwEm>
0x00001a8e <main+30>: mov %eax,-0x14(%ebp)
0x00001a91 <main+33>: mov -0x14(%ebp),%eax
0x00001a94 <main+36>: mov %eax,(%esp)
0x00001a97 <main+39>: movl $0x1,0x4(%esp)
0x00001a9f <main+47>: movl $0x2,0x8(%esp)
0x00001aa7 <main+55>: call 0x1bb0 <_ZN1AC1Eii>
0x00001aac <main+60>: mov -0x14(%ebp),%eax
0x00001aaf <main+63>: mov %eax,-0x18(%ebp)
0x00001ab2 <main+66>: lea -0x28(%ebp),%eax
0x00001ab5 <main+69>: mov %eax,(%esp)
0x00001ab8 <main+72>: movl $0x1,0x4(%esp)
0x00001ac0 <main+80>: movl $0x2,0x8(%esp)
0x00001ac8 <main+88>: call 0x1bb0 <_ZN1AC1Eii>
0x00001acd <main+93>: mov -0x18(%ebp),%eax
0x00001ad0 <main+96>: mov %eax,(%esp)
0x00001ad3 <main+99>: call 0x1d94 <dyld_stub__ZdlPv>
0x00001ad8 <main+104>: movl $0x0,-0x10(%ebp)
0x00001adf <main+111>: mov -0x10(%ebp),%eax
0x00001ae2 <main+114>: mov %eax,-0xc(%ebp)
0x00001ae5 <main+117>: mov -0xc(%ebp),%eax
0x00001ae8 <main+120>: add $0x38,%esp
0x00001aeb <main+123>: pop %ebp
0x00001aec <main+124>: ret
可以看出,二者的不同之处在于如果是局部变量,不需要执行申请对象所需空间的函数,直接调用构造函数。当然,很容易理解,栈已经为它提供了空间。
Q: 既然可以使用operator new单独申请空间,然后单独调用构造函数,那么可否单独调用析构函数,然后释放内存?
A: 是的。
#include <iostream>
using namespace std;
#define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl;
class A
{
public:
A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { }
int value_one() const { return _value_one; }
int value_two() const { return _value_two; }
virtual void show() const { std::cout << "show A..." << std::endl; }
void * operator new(size_t size)
{
std::cout << "operator new is called..." << std::endl;
return malloc(size);
}
void operator delete(void *p)
{
free(p);
}
private:
int _value_one;
int _value_two;
};
int main (int argc, const char * argv[])
{
A *a = new A(1, 2);
a->A::~A(); // call deconstruct function
free(a); // free the mem
return 0;
}
运行ok.
xichen
2012-6-2 22:59:05