request-free-img

透视C++ new操作符:类对象动态创建的底层原理

TestClass* p = new TestClass();

这样可以动态创建一个C++的类对象。

这个人人都会,然而这简单的1行代码底层到底是如何创建这个类对象的呢?

今天这个视频我要带着你透视C++类的底层原理。不管你是新手还是老手,都建议你看一看,这可以让你知其然知其所以然。

示例类定义

class TestClass {
public:
    TestClass(int n):memberN(n) {
    }
    ~TestClass() {
    }
    void Bark() {
        std::cout << "Wang Wang" << std::endl;
    }
private:
    int memberN;
};

这是一个简单的C++类,有一个构造函数,有一个析构函数,还有一个Bark的成员函数。

动态创建对象

    TestClass* p = new TestClass(1);
    p->Bark();

现在我们在这里设置断点,然后运行这个程序,再进入汇编模式。要想理解底层原理,是离不开汇编代码的。

new 操作符的汇编实现

    TestClass* p = new TestClass(1);
00242820 push 4
00242822 call operator new (0241145h)
00242827 add esp,4
0024282A mov dword ptr [ebp-0FCh],eax
00242830 mov byte ptr [ebp-4],1
00242834 cmp dword ptr [ebp-0FCh],0
0024283B je __$EncStackInitStart+78h (0242852h)
0024283D push 1
0024283F mov ecx,dword ptr [ebp-0FCh]
00242845 call TestClass::TestClass (02414DDh)
0024284A mov dword ptr [ebp-110h],eax
00242850 jmp __$EncStackInitStart+82h (024285Ch)
00242852 mov dword ptr [ebp-110h],0
0024285C mov eax,dword ptr [ebp-110h]
00242862 mov dword ptr [ebp-0F0h],eax
00242868 mov byte ptr [ebp-4],0
0024286C mov ecx,dword ptr [ebp-0F0h]
00242872 mov dword ptr [p],ecx

首先将参数4压栈,它用于接下来调用new申请4个字节的内存。因为我们这个类中只有一个int型成员变量,所以它只占用4个字节的长度。

new操作符调用完成后申请的内存会保存在eax寄存器中,这里是将eax也就是申请的内存保存到局部变量中。

接着比较申请的内存地址是否为空,如果为空,就是申请内存失败了,那么就跳到错误处理。

接下来把参数1压栈,它是传递给构造函数的参数。然后把前边申请的内存加载到ecx寄存器中做为参数传递给构造函数。

关键点:this指针

请注意,虽然我们的代码中,这个类对象只有一个参数,但是从汇编代码中可以看出,它向类的构造函数传递了2个参数。而第一个参数就是申请的内存,其实就是类的this指针。

划重点:

在C++中,类的this指针总是作为第一个隐式参数传递给成员函数,包括构造函数和析构函数。

构造函数内部执行

然后进入到类的构造函数中,这几行是栈帧的处理,我们以前的视频中讲过,忘记的同学可以再翻回之前的视频。

然后将未初始化的内存赋值为CC,再把THIS指针存储到当前对象的地址。后边是检查调试器。然后就是将传进来的参数n的值赋给成员变量memberN。

成员函数调用

    p->Bark();
00242875 mov ecx,dword ptr [p]
00242878 call TestClass::Bark (02410C8h)

怎么样,关于C++的类对象创建过程,你现在明白了吗?

通过这一行看似简单的 new 代码,我们看到了内存申请、this指针传递、构造函数调用等完整的底层流程,这也是C++面向对象特性的核心实现机制。


更多问题探讨,请关注公众号:程序员角