关于C++ 的 this指针

关于this指针

在编程过程中,我们都使用过this指针,可是this指针究竟从何而来却很少有人知道,现在我们一起来看一下this指针的由来

测试代码如下:

class CTest
{
public:
    void SetNumber(int number)
    {
        m_nInt = number;
    }

    int m_nInt = 0;
};

int main(int argc, char *argv[])
{
    CTest test;
    test.SetNumber(5);
    printf("CTest : m_nInt = %d\n", test.m_nInt);

    system("pause");
    return 0;
}

对编译生成的程序进行反汇编:

main函数部分的汇编代码如下

int main(int argc, char *argv[])
{
01183CD0  push        ebp  
01183CD1  mov         ebp,esp  
01183CD3  sub         esp,0D0h  
01183CD9  push        ebx  
01183CDA  push        esi  
01183CDB  push        edi  
01183CDC  lea         edi,[ebp-0D0h]  
01183CE2  mov         ecx,34h  
01183CE7  mov         eax,0CCCCCCCCh  
01183CEC  rep stos    dword ptr es:[edi]  
01183CEE  mov         eax,dword ptr ds:[01188000h]  
01183CF3  xor         eax,ebp  
01183CF5  mov         dword ptr [ebp-4],eax  
    CTest test;
01183CF8  lea         ecx,[test]  
01183CFB  call        CTest::CTest (011811E5h)  
    test.SetNumber(5);
01183D00  push        5  
01183D02  lea         ecx,[test]  
01183D05  call        CTest::SetNumber (011810EBh)  
    printf("CTest : m_nInt = %d\n", test.m_nInt);
01183D0A  mov         esi,esp  
01183D0C  mov         eax,dword ptr [test]  
01183D0F  push        eax  
01183D10  push        1185858h  
01183D15  call        dword ptr ds:[1189118h]  
01183D1B  add         esp,8  
01183D1E  cmp         esi,esp  
01183D20  call        __RTC_CheckEsp (01181140h)  

    system("pause");
01183D25  mov         esi,esp  
01183D27  push        1185874h  
01183D2C  call        dword ptr ds:[1189110h]  
01183D32  add         esp,4  
01183D35  cmp         esi,esp  
01183D37  call        __RTC_CheckEsp (01181140h)  
    return 0;
01183D3C  xor         eax,eax  
}
01183D3E  push        edx  
01183D3F  mov         ecx,ebp  
01183D41  push        eax  
01183D42  lea         edx,ds:[1183D70h]  
01183D48  call        @_RTC_CheckStackVars@8 (01181087h)  
01183D4D  pop         eax  
01183D4E  pop         edx  
01183D4F  pop         edi  
01183D50  pop         esi  
01183D51  pop         ebx  
01183D52  mov         ecx,dword ptr [ebp-4]  
01183D55  xor         ecx,ebp  
01183D57  call        @__security_check_cookie@4 (0118101Eh)  
01183D5C  add         esp,0D0h  
01183D62  cmp         ebp,esp  
01183D64  call        __RTC_CheckEsp (01181140h)  
01183D69  mov         esp,ebp  
01183D6B  pop         ebp  
01183D6C  ret  

我们先简单理解一下上述的汇编代码:

01183CD0  push        ebp  
01183CD1  mov         ebp,esp  
01183CD3  sub         esp,0D0h  
01183CD9  push        ebx  
01183CDA  push        esi  
01183CDB  push        edi  

这是main函数中最开头的一部分代码,主要用于利用栈来保护现场,保存外部调用函数的基址(ebp寄存器中),并将其设置当前函数的基址,然后保存之后要用到的几个寄存器中的数据,这样当函数返回后,保证其调用函数可以继续正常向下执行。并将栈指针寄存器esp向栈低地址移动, 这主要用于开辟栈空间给当前函数块中使用。

01183CDC  lea         edi,[ebp-0D0h]  
01183CE2  mov         ecx,34h  
01183CE7  mov         eax,0CCCCCCCCh  
01183CEC  rep stos    dword ptr es:[edi]  

这部分代码主要用于初始化当前的栈空间,将esp 至 ebp 之间的内存全部初始化为0xCC(0xCC 表示当前内存暂未使用过)。

01183CEE  mov         eax,dword ptr ds:[01188000h]  
01183CF3  xor         eax,ebp  
01183CF5  mov         dword ptr [ebp-4],eax  

这段代码主要在基址指针上的前四个字节插入随机数,之后会利用这个随机数进行检查,防止栈溢出攻击。

    CTest test;
01183CF8  lea         ecx,[test]  
01183CFB  call        CTest::CTest (011811E5h)  

这里很明显开始调用CTest的构造函数,跟进去

CTest::CTest:
011811E5  jmp         CTest::CTest (011813D0h)  

发现这里执行的是一次跳转,再跟:

CTest::CTest:
011813D0  push        ebp  
011813D1  mov         ebp,esp  
011813D3  sub         esp,0CCh  
011813D9  push        ebx  
011813DA  push        esi  
011813DB  push        edi  
011813DC  push        ecx  
011813DD  lea         edi,[ebp-0CCh]  
011813E3  mov         ecx,33h  
011813E8  mov         eax,0CCCCCCCCh  
011813ED  rep stos    dword ptr es:[edi]  
011813EF  pop         ecx  
011813F0  mov         dword ptr [this],ecx  
011813F3  mov         eax,dword ptr [this]  
011813F6  mov         dword ptr [eax],0  
011813FC  mov         eax,dword ptr [this]  
011813FF  pop         edi  
01181400  pop         esi  
01181401  pop         ebx  
01181402  mov         esp,ebp  
01181404  pop         ebp  
01181405  ret  

这里可以看出是由编译器提供的默认构造函数的汇编指令,并在此处将m_nInt 初始化为0了,可见在类中初始化的变量,即使不是在初始化函数中初始化的成员变量,最后还是在构造函数中初始化。

    test.SetNumber(5);
01183D00  push        5  
01183D02  lea         ecx,[test]  
01183D05  call        CTest::SetNumber (011810EBh)  

lea 为加载地址指令
这里我们看一下test的地址


02.png

test的地址为0x0044fd74,在内存中此时成员m_nInt已被初始化为0了(如下图):

01.png

而基址指针ebp为0x0044fd80,比test的首地址大12(8+4),即对象的大小+随机数保存位置的内存大小。
这里是VS的优化后显示的结果,实际应该是 [test] 等价于 [ebp - 12]

所以在调用函数之前不但传入了5,还通过ecx (隐含地)传入了对象的首地址,即所谓的“this指针”

接着看函数内部的实现,以证明上述观点!

同理由此jmp跳转,直接看内部实现:

    void SetNumber(int number)
    {
01181420  push        ebp  
01181421  mov         ebp,esp  
01181423  sub         esp,0CCh  
01181429  push        ebx  
0118142A  push        esi  
0118142B  push        edi  
0118142C  push        ecx  
0118142D  lea         edi,[ebp-0CCh]  
01181433  mov         ecx,33h  
01181438  mov         eax,0CCCCCCCCh  
0118143D  rep stos    dword ptr es:[edi]  
0118143F  pop         ecx  
01181440  mov         dword ptr [this],ecx  
        m_nInt = number;
01181443  mov         eax,dword ptr [this]  
01181446  mov         ecx,dword ptr [number]  
01181449  mov         dword ptr [eax],ecx  
    }
0118144B  pop         edi  
0118144C  pop         esi  
0118144D  pop         ebx  
0118144E  mov         esp,ebp  
01181450  pop         ebp  
01181451  ret         4  

这段代码一直到 0118143D所在行都是日常任务,开辟并初始化栈空间。

0118143F  pop         ecx  
01181440  mov         dword ptr [this],ecx  

从这行开始真正的工作了,这里将外部传进来得对象首地址存进了 “this”中,我们查看一下this的首地址是多少
在当前ebp之上,保存着ecx中传入的0x0044fd74的值之处就是this指针的首地址。

03.png
04.png

从04图中可以看到0x0044FC90 即为栈上为this分配的内存的首地址,这里将ecx中保存的对象所在的首地址(即指针)保存在了为this所分配的内存中,指针本身占用4个字节,由于内存对齐,此处编译器分配了8个字节。0x0044FC90 + 8 正好等于ebp中保存的值。

01181443  mov         eax,dword ptr [this]  
01181446  mov         ecx,dword ptr [number]  
01181449  mov         dword ptr [eax],ecx  

再看看[number] 指代的是什么(当然是形参number啦,但实际反汇编时不会这么明显,我们来看看实际到底指代的是什么)

由前面代码推得,在进入函数前,5已经压人栈中,所以5的地址必然大于当前的ebp的值,由图4可见number的地址为ebp+8。

ebp为首地址的指针为调用该函数的函数的栈的基址。
ebp + 4 为调用该函数处的下一行指令的的地址,用于ret 返回时使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342