zoukankan      html  css  js  c++  java
  • 用汇编的眼光看C++(之指针2) 四

    【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】

        (4)指针和引用

        引用是C++和语言的区别之一。其实本质上说两者是一致的。朋友们可以看下面两段代码。

        a)指针和指针的函数代码

    1. void add_point(int* q)  
    2. {  
    3.     (*q)++;  
    4. }  
    5.   
    6. void add_ref(int& q)  
    7. {  
    8.     q++;  
    9. }  


        b)函数的调用代码

    1. 56:       int m = 10;  
    2. 004012E8   mov         dword ptr [ebp-4],0Ah  
    3. 57:       add_point(&m);  
    4. 004012EF   lea         eax,[ebp-4]  
    5. 004012F2   push        eax  
    6. 004012F3   call        @ILT+45(process) (00401032)  
    7. 004012F8   add         esp,4  
    8. 58:       add_ref(m);  
    9. 004012FB   lea         ecx,[ebp-4]  
    10. 004012FE   push        ecx  
    11. 004012FF   call        @ILT+50(add_ref) (00401037)  
    12. 00401304   add         esp,4  
    13. 59:       return 1;  
    14. 00401307   mov         eax,1  
    15. 60:   }  

        分析一下,我们发现其实函数add_point和函数add_ref实现的功能,都是对输入的数据进行自增处理。只不过处理的时候,一个函数的入参是指针,一个函数的入参是引用。在函数调用的地方,大家可以发现指针和引用居然是一样的。首先看add_point,第一句获取m的地址复制给eax,第二句压栈处理,第三句调用函数add_point,第四句出栈回溯。同样看一下add_ref,第一句获取m的地址复制给ecx,第二句ecx压栈处理,第三句调用函数add_ref,第四句堆栈回溯处理。相信看到这里,大家就明白C++的前辈们为什么鼓励大家多多使用引用了。

        (5)指针和结构体

        我们在学习数据节点的时候,相信大家都学习过这样的一个数据结构定义:

    1. typedef struct _NODE  
    2. {  
    3.     int data;  
    4.     struct _NODE* next;  
    5. }NODE;  

        当时,我们都不明白这个结构体是什么意思?其实这个定义完全修改成这样:

    1. typedef struct _NODE  
    2. {  
    3.     int data;  
    4.     void* next;  
    5. }NODE;  

         这两个数据结构体其实是完全一致的。第一个数据保存数据,第二个数据为指针,内容为某一个数据类型的地址。这种确定的地址和void*类型的地址类型是一样的。只不过前面一种更加直接。后面一种地址的固然方便,但是使用的时候每一次都需要进行转换,很是麻烦。如果大家感兴趣,不妨是接着看下面一道题目:

    1. typedef struct _NODE  
    2. {  
    3.     struct _NODE* next;  
    4. }NODE;  

        我们既可以把节点NODE的地址看是NODE*,也可以堪称是NODE**,两者之间有差别吗?(其实没有区别

        linux 内核代码上面有一种计算偏移值的方法,大家可以参考一下:

    1. int offset = (int)&(((NODE*)(0))->next);  

        (6)class指针

         class指针比较复杂,不过大家可以从一个小范例看出一些端倪:

    1. class fruit  
    2. {  
    3. public:  
    4.     fruit() {}  
    5.     ~fruit() {}  
    6.     void print() {printf("fruit!\n");}  
    7. };  
    8.   
    9. class apple : public fruit  
    10. {  
    11. public:  
    12.     apple() {}  
    13.     ~apple() {}  
    14.     void print() {printf("apple!\n");}  
    15. };  
    16.   
    17. void process()  
    18. {  
    19.     fruit f;  
    20.     apple* a = (apple*)&f;  
    21.     a->print();  
    22.     fruit* b = &f;  
    23.     b->print();  
    24. }  

        熟悉C++的朋友可以很快知道这道题目的答案了,那么为什么a和b都指向同一个地址,使用了相同的print函数,但是结果不同。我想这主要是因为两者数据类型不同的缘故。一旦指针和某一种数据类型绑在了一起,那么这个指针的所有行为事实上都已经被这种类型的数据所限定了。普通数据类型是这样,自定义的class类型也是这样。只要不是在print函数前面加上virtual,我们就会发现两个print的调用都是硬编码,和普通的函数调用无异。所以说,指针离不开数据类型,离开具体类型的地址是没有意义的。就像void*是可以原谅的,但是void却是万万不能接受的。下面的汇编代码很好的说明了这一点。

    1. 65:       fruit f;  
    2. 0040132D   lea         ecx,[ebp-10h]  
    3. 00401330   call        @ILT+35(fruit::fruit) (00401028)  
    4. 00401335   mov         dword ptr [ebp-4],0  
    5. 66:       apple* a = (apple*)&f;  
    6. 0040133C   lea         eax,[ebp-10h]  
    7. 0040133F   mov         dword ptr [ebp-14h],eax  
    8. 67:       a->print();  
    9. 00401342   mov         ecx,dword ptr [ebp-14h]  
    10. 00401345   call        @ILT+0(apple::print) (00401005)  
    11. 68:       fruit* b = &f;  
    12. 0040134A   lea         ecx,[ebp-10h]  
    13. 0040134D   mov         dword ptr [ebp-18h],ecx  
    14. 69:       b->print();  
    15. 00401350   mov         ecx,dword ptr [ebp-18h]  
    16. 00401353   call        @ILT+25(fruit::print) (0040101e)  

    (全文完)

     

  • 相关阅读:
    机器学习--强化学习
    机器学习--深度学习
    机器学习--维度灾难
    机器学习--最优化
    机器学习--降维
    机器学习--聚类
    机器学习--模型提升
    Git和gitHub用户名 邮箱
    Git线上操作
    版本控制器:Git
  • 原文地址:https://www.cnblogs.com/sier/p/5676477.html
Copyright © 2011-2022 走看看