浅析C++中的this指针

    技术2025-07-15  14

    浅析C++中的this指针

    在编写C++程序的时候,我们可以不需要知道this指针的存在,但是,如果你想深入了解c++.。或者从事软件逆向。这里我们是很有必要知道的。下面,我们就一步一步的探究this指针的秘密。

    为何引入this指针:

    类中每个数据成员对该类的每个对象都有一个拷贝,要在内存中为其划分一块内存单元,分别存储从类中拷贝的数据成员,静态变量除外,因为类的每个对象都可以共享静态变量。而类的成员函数对该类的每个对象只有一个拷贝。对于该成员函数类来讲,它使用类辨别进行哪个对象的操作。因此C++语言引入了this指针,每个成员函数都隐含这一个常量指针类型的型参,即this指针。

    概念:

    This指针是类或者结构体,联合体中的成员函数中隐含存在的指针。它指向一个对象,来指出具体的是哪个对象的成员函数被调用了。值得注意的是静态成员函数中不存在this指针。当一个类对象的非静态数据成员被调用的时候,对象的地址作为一个隐含的存数被传递给函数。举个例子:myDate.setMonth(3);我们可以将其翻译成如下的形式:setMonth(&myDate,3);注意:修改this指针在最近版本的C++中是不合法的。This指针的一个实例:Void Date::setMonth(int mn){Month =mn;this->month=mn;(*this).month=mn;}例子中,这三种声明方式相等的,为何相等,看了如下的反汇编,你会清楚!

     

    反汇编:

    #include<iostream>using namespace std;class feng{ static int a; int b; int c;public: feng(int b,int c) {    this->b=b;  this->c=c; } static void out()

     { cout<<a<<endl;

     }  int outof() {  cout<<b<<" "<<c<<endl;  return 0; } virtual int getout() {  return a; }};int feng::a=1234;

    void main(){ feng xiang(13,14); xiang.out(); xiang.outof(); cout<<xiang.getout()<<endl;}

    源码中,我建立了三种不同的成员函数,以此来研究在静态成员函数,普通类成员函数,和虚函数中,this指针是否存在,及存在形式。

    下面是相应的main代码及注释:    proc near               ; CODE XREF: _mainj.text:004015A0.text:004015A0 var_4C          = byte ptr -4Ch.text:004015A0 object          = byte ptr -0Ch.text:004015A0.text:004015A0                 push    ebp             ; 改名为object是为了一看就知道是一个对象的存放地址变量.text:004015A1                 mov     ebp, esp.text:004015A3                 sub     esp, 4Ch.text:004015A6                 push    ebx.text:004015A7                 push    esi.text:004015A8                 push    edi.text:004015A9                 lea     edi, [ebp+var_4C].text:004015AC                 mov     ecx, 13h.text:004015B1                 mov     eax, 0CCCCCCCCh.text:004015B6                 rep stosd               ; 上面的操作是内存初始化过程.text:004015B8                 push    14.text:004015BA                 push    13              ; 将对象的两个操作数压栈,转换为十进制便于识别。.text:004015BC                 lea     ecx, [ebp+object] ; 将对象存放的首地址传递给exc寄存器.text:004015BF                 call    j_feng__feng    ; 实例化类对象。.text:004015C4                 call    j_feng__out     ; 这里是静态成员函数的调用。.text:004015C9                 lea     ecx, [ebp+object] ; 实例化对象的首地址传给ecx。.text:004015CC                 call    j_feng__outof   ; 调用普通类成员函数,outog().text:004015D1                 push    offset loc_4010C8.text:004015D6                 lea     ecx, [ebp+object] ; 实例化对象的首地址再次传给ecx。.text:004015D9                 call    j_feng__getout  ; 调用虚函数getout().text:004015DE                 push    eax             ; 虚函数的返回值传到了eax中,并入栈,为后面的输出作准备。.text:004015DF                 mov     ecx, offset std__cout ;  c++标准输入输出流库函数cout压栈.text:004015E4                 call    sub_401104      ; 执行数据输出操作,返回值保存到eax中.text:004015E9                 mov     ecx, eax        ; 输出操作入栈。.text:004015EB                 call    j_std__basic_ostream_char_std__char_traits_char_____operator__ ; 这里长长的调用是C++标准库文件函数,在这里的用途是:将有多个连续的数据输出时,与压入参数顺序相反输出同时输出结束符:endl.text:004015F0                 pop     edi.text:004015F1                 pop     esi.text:004015F2                 pop     ebx.text:004015F3                 add     esp, 4Ch.text:004015F6                 cmp     ebp, esp.text:004015F8                 call    __chkesp        ; 到这里检查堆栈是否平衡,和程序结束的收尾工作。.text:004015FD                 mov     esp, ebp.text:004015FF                 pop     ebp.text:00401600                 retn.text:00401600 main            endp.text:00401600

    下面我们一一来看三个成员函数调用。先看第二个普通成员函数的调用。xiang.outof();对应其代码:.text:004015C9                 lea     ecx, [ebp+object].text:004015CC                 call    j_feng__outof  我们看到,在调用outof()函数之前,ecx中压入了类对象的地址。通过调试,我们看看ecx中的状态。

    图1.如图1所示,在调试中我们发现,此时ecx中存放的是object。也就是存放的是类实例化对象的地址。我们进入函数内部去看ecx的作用。.text:004016E0 feng__outof proc near                   ; CODE XREF: j_feng__outofj.text:004016E0.text:004016E0 var_44= byte ptr -44h.text:004016E0 var_4= dword ptr -4.text:004016E0.text:004016E0 push    ebp.text:004016E1 mov     ebp, esp.text:004016E3 sub     esp, 44h                        ; Integer Subtraction.text:004016E6 push    ebx.text:004016E7 push    esi.text:004016E8 push    edi.text:004016E9 push    ecx.text:004016EA lea     edi, [ebp+var_44]               ; Load Effective Address.text:004016ED mov     ecx, 11h.text:004016F2 mov     eax, 0CCCCCCCCh.text:004016F7 rep stosd                               ; Store String这里是函数初始化部分。.text:004016F9 pop     ecx.text:004016FA mov     [ebp+var_4], ecx此时,我们将ecx弹出堆栈,并且将其放到ebp-4的地址中。.text:004016FD push    offset loc_4010C8.text:00401702 mov     eax, [ebp+var_4].text:00401705 mov     ecx, [eax+8]这里将对象地址保存到了eax中。同时通过eax(即对象地址)+8取出参数14放入ecx中保存。.text:00401708 push    ecx.text:00401709 push    offset asc_46F020               ; " "这两句时将参数14 ,和空格符“ ”压栈。.text:0040170E mov     edx, [ebp+var_4].text:00401711 mov     eax, [edx+4].text:00401714 push    eax而这里将对象保存到edx中,通过对象地址+4取出参数13放入eax中,同时将数据压栈。.text:00401715 mov     ecx, offset std__cout.text:0040171A call    sub_401104                      ; Call Procedure.text:0040171F push    eax到这里,是C++标准库文件实现cout操作,输出13。并将cout操作压栈.text:00401720 call    j_std__operator__               ; Call Procedure实现空格输出。.text:00401725 add     esp, 8                          ; Add这里为后面调用sub_401104 做准备。.text:00401728 mov     ecx, eax.text:0040172A call    sub_401104                      ; Call Procedure将cout操作保存到ecx中,同时,调用sub_401104 操作,输出14..text:0040172F mov     ecx, eax.text:00401731 call    j_std__basic_ostream_char_std__char_traits_char_____operator__ ; Call Procedure这里是实现一串数据输出的收尾工作。相当于<<endl操作。.text:00401736 xor     eax, eax                        ; Logical Exclusive OR寄存器eax清零。.text:00401738 pop     edi.text:00401739 pop     esi.text:0040173A pop     ebx.text:0040173B add     esp, 44h                        ; Add.text:0040173E cmp     ebp, esp                        ; Compare Two Operands.text:00401740 call    __chkesp                        ; Call Procedure.text:00401745 mov     esp, ebp.text:00401747 pop     ebp函数结束收尾工作。

    通过对这里普通成员函数的调用,我们发现。当类对象在给实例化对象的普通成员函数传送数据成员的时候,是通过对对象的地址加上数据成员所在的偏移地址来传送的。这里如果不知道对象的地址,我们普通数据成员函数是无法从类初始化对象中获得相应的数据成员的。而这个作用也正是this指针的作用,让普通数据成员函数与类对象有了间接的联系方式——通过this指针。

    下面我们来看静态成员函数是如何访问数据的。xiang.out();此函数为静态成员函数,其调用方式我们看到直接text:004015C4           call    j_feng__out     函数调用之前,并不是和普通成员函数一样现将相应对象的地址放入到ecx中。也就是说,静态成员函数并不需要什么方式来寻找调用它的对象。而这时静态成员函数的属性来决定的。静态成员具有共享的特征,也就是说,当程序建立,如果类中有静态成员函数,或数据,它一旦初始化,无论有多少个对象建立,都只有一个内存存储空间。也就是这样,我们调用out()函数时,不需要与对象有任何联系,如果任何对象中对静态成员数据有所更新,静态数据成员会更新,不产生副本。

    虚函数的调用。xiang.getout()尽管在虚函数的函数调用的方式上是与普通函数有所不同,但是,我们通过对代码的观察我们会发现,成员函数的数据在与对象发生“关系”(这里是指有数据传输)时,同样需要this指针来指明是哪个对象的什么数据成员被那个成员函数调用。但是getout()内部,我们调用的确是静态成员函数,所以,没有用到this指针。虚函数的调用方式,在“浅析c++中虚函数的调用”详细揭示了其原理。

    小结:类中,this指针的实质就是指向实例对象的指针。它作用于类非静态成员。它是类中对象与非静态成员函数的一个发生相互“关系”的纽带。通常在调用非静态成员函数时,this指针通过ecx传入到函数中。函数中通常需要用到this的值来访问相关的数据。

    最新回复(0)