#include <iostream>

using namespace std;

class B {
public:
void foo() { cout << "B foo " << endl; }
void pp() { cout << "B pp" << endl; }
void FunctionB() { cout << "funB" << endl; }
}; int main()
{
B *somenull = NULL;
somenull->foo();
somenull->pp();
somenull->FunctionB(); return 0;
} 为什么 somenull 为空指针,还能执行通过呢?

能够阐明“静态绑定”和“动态绑定”的差别。

真正的原因是:由于对于非虚成员函数,C++这门语言是静态绑定的。

这也是C++语言和其他语言Java, Python的一个显著差别。

以此以下的语句为例:

somenull->foo();

这语句的意图是:调用对象somenull的foo成员函数。

假设这句话在Java或Python等动态绑定的语言之中,编译器生成的代码大概是:

找到somenull的foo成员函数。调用它。

(注意,这里的找到是程序执行的时候才找的,这也是所谓动态绑定的含义:执行时才绑定这个函数名与其相应的实际代码。

有些地方也称这样的机制为迟绑定。晚绑定。)

可是对于C++。为了保证程序的执行时效率,C++的设计者觉得凡是编译时能确定的事情,就不要拖到执行时再查找了。所以C++的编译器看到这句话会这么干:

1:查找somenull的类型,发现它有一个非虚的成员函数叫foo。(编译器干的)

2:找到了。在这里生成一个函数调用,直接调B::foo(somenull)。

所以到了执行时,因为foo()函数里面并没有不论什么须要解引用somenull指针的代码,所以真实情况下也不会引发segment fault。这里对成员函数的解析,和查找其相应的代码的工作都是在编译阶段完毕而非执行时完毕的,这就是所谓的静态绑定。也叫早绑定。

正确理解C++的静态绑定能够理解一些特殊情况下C++的行为。

this 指针是空指针 不去骚扰他 他就不搞死你

你敢动他试试

假设还没有看烦,能够參考以下的这些东西。

有以下的一个简单的类:

class CNullPointCall

{

public:

    static void Test1();

    void Test2();

    void Test3(int iTest);

    void Test4();



private:

    static int m_iStatic;

    int m_iTest;

};



int CNullPointCall::m_iStatic = 0;



void CNullPointCall::Test1()

{

    cout << m_iStatic << endl;

}



void CNullPointCall::Test2()

{

    cout << "Very Cool!" << endl; 

}



void CNullPointCall::Test3(int iTest)

{

    cout << iTest << endl; 

}



void CNullPointCall::Test4()

{

    cout << m_iTest << endl; 

}

那么以下的代码都正确吗?都会输出什么?

CNullPointCall *pNull = NULL; // 没错,就是给指针赋值为空

pNull->Test1(); // call 1

pNull->Test2(); // call 2

pNull->Test3(13); // call 3

pNull->Test4(); // call 4

你肯定会非常奇怪我为什么这么问。

一个值为NULL的指针怎么能够用来调用类的成员函数呢?。但是实事却非常让人惊讶:除了call 4那行代码以外,其余3个类成员函数的调用都是成功的。都能正确的输出结果。并且包括这3行代码的程序能非常好的执行。

    经过细心的比較就能够发现,call 4那行代码跟其它3行代码的本质差别:类CNullPointCall的成员函数中用到了this指针。

对于类成员函数而言,并非一个对象相应一个单独的成员函数体,而是此类的全部对象共用这个成员函数体。 当程序被编译之后。此成员函数地址即已确定。而成员函数之所以能把属于此类的各个对象的数据差别开, 就是靠这个this指针。函数体内全部对类数据成员的訪问, 都会被转化为this->数据成员的方式。

而一个对象的this指针并非对象本身的一部分。不会影响sizeof(“对象”)的结果。this作用域是在类内部,当在类的非静态成员函数中訪问类的非静态成员的时候。编译器会自己主动将对象本身的地址作为一个隐含參数传递给函数。也就是说,即使你没有写上this指针。编译器在编译的时候也是加上this的。它作为非静态成员函数的隐含形參。对各成员的訪问均通过this进行。

对于上面的样例来说,this的值也就是pNull的值。也就是说this的值为NULL。

而Test1()是静态函数,编译器不会给它传递this指针,所以call 1那行代码能够正确调用(这里相当于CNullPointCall::Test1())。对于Test2()和Test3()两个成员函数,尽管编译器会给这两个函数传递this指针,可是它们并没有通过this指针来訪问类的成员变量,因此call 2和call 3两行代码能够正确调用;而对于成员函数Test4()要訪问类的成员变量,因此要使用this指针,这个时候发现this指针的值为NULL。就会造成程序的崩溃。    

    事实上,我们能够想象编译器把Test4()转换成例如以下的形式:

void CNullPointCall::Test4(CNullPointCall *this)

{

    cout << this->m_iTest << endl; 

}

而把call 4那行代码转换成了以下的形式:

CNullPointCall::Test4(pNull);

所以会在通过this指针訪问m_iTest的时候造成程序的崩溃。

    以下通过查看上面代码用VC 2005编译后的汇编代码来详解一下奇妙的this指针。

    上面的C++代码编译生成的汇编代码是以下的形式:

    CNullPointCall *pNull = NULL;

0041171E  mov         dword ptr [pNull],0 

    pNull->Test1();

00411725  call        CNullPointCall::Test1 (411069h) 

    pNull->Test2();

0041172A  mov         ecx,dword ptr [pNull] 

0041172D  call        CNullPointCall::Test2 (4111E0h) 

    pNull->Test3(13);

00411732  push        0Dh  

00411734  mov         ecx,dword ptr [pNull] 

00411737  call        CNullPointCall::Test3 (41105Ah) 

    pNull->Test4();

0041173C  mov         ecx,dword ptr [pNull] 

0041173F  call        CNullPointCall::Test4 (411032h) 

通过比較静态函数Test1()和其它3个非静态函数调用所生成的的汇编代码能够看出:非静态函数调用之前都会把指向对象的指针pNull(也就是this指针)放到ecx寄存器中(mov ecx,dword ptr [pNull])。这就是this指针的特殊之处。看call 3那行C++代码的汇编代码就能够看到this指针跟一般的函数參数的差别:一般的函数參数是直接压入栈中(push 0Dh)。而this指针却被放到了ecx寄存器中。

在类的非成员函数中假设要用到类的成员变量,就能够通过訪问ecx寄存器来得到指向对象的this指针。然后再通过this指针加上成员变量的偏移量来找到对应的成员变量。

以下再通过另外一个样例来说明this指针是如何被传递到成员函数中和如何使用this来訪问成员变量的。

    依旧是一个非常easy的类:

class CTest

{

public:

    void SetValue();



private:

    int m_iValue1;

    int m_iValue2;

};



void CTest::SetValue()

{

    m_iValue1 = 13;

    m_iValue2 = 13;

}

用例如以下的代码调用成员函数:

CTest test;

test.SetValue();

上面的C++代码的汇编代码为:

    CTest test;

    test.SetValue();

004117DC  lea         ecx,[test] 

004117DF  call        CTest::SetValue (4111CCh) 

相同的,首先把指向对象的指针放到ecx寄存器中;然后调用类CTest的成员函数SetValue()。

地址4111CCh那里存放的事实上就是一个转跳指令,转跳到成员函数SetValue()内部。

004111CC  jmp         CTest::SetValue (411750h)

而411750h才是类CTest的成员函数SetValue()的地址。

void CTest::SetValue()

{

00411750  push        ebp  

00411751  mov         ebp,esp 

00411753  sub         esp,0CCh 

00411759  push        ebx  

0041175A  push        esi  

0041175B  push        edi  

0041175C  push        ecx // 1   

0041175D  lea         edi,[ebp-0CCh] 

00411763  mov         ecx,33h 

00411768  mov         eax,0CCCCCCCCh 

0041176D  rep stos    dword ptr es:[edi] 

0041176F  pop         ecx // 2 

00411770  mov         dword ptr [ebp-8],ecx // 3

    m_iValue1 = 13;

00411773  mov         eax,dword ptr [this] // 4

00411776  mov         dword ptr [eax],0Dh // 5

    m_iValue2 = 13;

0041177C  mov         eax,dword ptr [this] // 6

0041177F  mov         dword ptr [eax+4],0Dh // 7

}

00411786  pop         edi  

00411787  pop         esi  

00411788  pop         ebx  

00411789  mov         esp,ebp 

0041178B  pop         ebp  

0041178C  ret 

以下对上面的汇编代码中的重点行进行分析:

    1、将ecx寄存器中的值压栈,也就是把this指针压栈。

    2、ecx寄存器出栈,也就是this指针出栈。

3、将ecx的值放到指定的地方,也就是this指针放到[ebp-8]内。

4、取this指针的值放入eax寄存器内。

此时,this指针指向test对象,test对象仅仅有两个int型的成员变量,在test对象内存中连续存放。也就是说this指针眼下指向m_iValue1。

    5、给寄存器eax指向的地址赋值0Dh(十六进制的13)。事实上就是给成员变量m_iValue1赋值13。

    6、同4。

    7、给寄存器eax指向的地址加4的地址赋值。在4中已经说明,eax寄存器内存放的是this指针,而this指针指向连续存放的int型的成员变量m_iValue1。

this指针加4(sizeof(int))也就是成员变量m_iValue2的地址。

因此这一行就是给成员变量m_iValue2赋值。

    通过上面的分析。我们能够从底层了解了C++中this指针的实现方法。

尽管不同的编译器会使用不同的处理方法。可是C++编译器必须遵守C++标准,因此对于this指针的实现应该都是几乎相同的。

最新文章

  1. JavaScript 动态脚本
  2. HDU 4006 优先队列
  3. hdoj 3501
  4. Oracle 截取、查找字符函数(持续更新)
  5. socket调用流程的函数及数据结构
  6. CcTalk (网络协议)(转)
  7. IE9 以下版本浏览器兼容HTML5的方法,使用百度静态资源的html5shiv包
  8. 【09】绝不在构造和析构过程中调用virtual方法
  9. poj 3261 Milk Patterns(后缀数组)(k次的最长重复子串)
  10. jQuery模板插件jsrender
  11. 下载visual studio 的离线包
  12. 为什么内部类访问的外部变量需要使用final修饰
  13. Django中Q搜索的简单应用
  14. 微信小程序windowHeight的值在ios和android平台不一致问题解决办法
  15. wx小程序-起航!
  16. 在12C上创建wm_concat函数
  17. maven执行单元测试失败后,继续生成Jacoco&amp;Sonar报告
  18. python基础----&gt;python的使用(三)
  19. PHP获取6位数随机数,获取redis里面不存在的6位随机数(设置24小时过时)
  20. [转载] MATLAB快捷键

热门文章

  1. SQL解析器的性能測试
  2. Kinect 开发 —— 常见手势识别(上)
  3. Vue自定义指令实现下拉加载:v-loadmore
  4. vsphere client和vsphere web client的区别
  5. Service-监听手机来电
  6. Android 多种方式正确的载入图像,有效避免oom
  7. 堆-heap
  8. 76.CGI编码
  9. 洛谷P1316 丢瓶盖
  10. Ajax : load()