• C++ debug时,将未初始化变量都赋值为CC的理解


    一直以来,我都不是太理解这种方式,在C++函数调用原理理解中,我仅仅是简单的认为,那么做,可能是因为CC平时用的少,而且好看:)所以初始化这样一个不怎么常用的变量,可以让人很快发现。。。。事实上,的确有这样的效果,当Debug时,我看一个变量为CC时的确第一时间就能反应过来,我又犯了一个不可饶恕的低级错误,又忘了初始化了,这点在变量为指针类型的时候更加严重。

    但是,在学习过反汇编这么久后,今天在看《C缺陷与陷阱》时,突然顿悟了CC的意义。。。。。至于为什么是看这本和这件事完全没有关系的时候突然想到,我也不知道,反正就是那样发生了。

    CC在汇编代码中表示为int 3,实际表示一个中断,在与硬件中断(CPU中加入的DR寄存器指示)做区别的时候也叫软中断。。。。几乎所有的调试工具在调试时,都是靠int 3来完成任务的。。。。。。这些我知道时间不短了。。。。但是今天才将其与VS在debug时的初始化联系起来。。。。。这样的话,假如有异常的跳转,程序运行到了不应该运行的地方。。。。那么,就会触发中断,让调试程序获得控制,这样可以更早的发现问题,而不是当运行了一堆莫名其妙的代码后才出现问题。。。。。。

    至于VS在debug时的初始化,可以用debug方式编译任何程序,你都能看到

    比如在C++函数调用原理理解例子中如下:

    0041136C lea edi,[ebp-0C0h] ;读入[ebp-0C0h]有效地址,即原esp-0C0h,正好是为该函数留出的临时存储区的最低位

    00411372 mov ecx,30h ;ecx = 30h(48),30h*4 = 0C0h

    00411377 mov eax,0CCCCCCCCh ;eax = 0CCCCCCCCh;

    0041137C rep stos dword ptr es:[edi] ;重复在es:[edi]存入30个;0CCCCCCCCh? Debug模式下把Stack上的变量初始化为0xcc,检查未初始化的问题

    C++函数调用原理理解

    ;空程序:
    int main()
    {
    00411360 push ebp ;压入ebp
    00411361 mov ebp,esp ;ebp = esp,保留esp,待函数调用完再恢复,因为函数调用中肯定会用到esp.
    00411363 sub esp,0C0h ;esp-=0C0h(192);为该函数留出临时存储区
    ;将其他指针或寄存器中的值入栈,以便在函数中使用这些寄存器。
    00411369 push ebx ;压入ebx
    0041136A push esi ;压入esi
    0041136B push edi ;压入edi
    0041136C lea edi,[ebp-0C0h] ;读入[ebp-0C0h]有效地址,即原esp-0C0h,正好是为该函数留出的临时存储区的最低位
    00411372 mov ecx,30h ;ecx = 30h(48),30h*4 = 0C0h
    00411377 mov eax,0CCCCCCCCh ;eax = 0CCCCCCCCh;
    0041137C rep stos dword ptr es:[edi] ;重复在es:[edi]存入30个;0CCCCCCCCh? Debug模式下把Stack上的变量初始化为0xcc,检查未初始化的问题
    return 0;
    0041137E xor eax,eax ;将eax清零,作为返回值
    }
    ;各指针出栈
    00411380 pop edi ;弹出edi
    00411381 pop esi ;弹出esi
    00411382 pop ebx ;弹出ebx
    00411383 mov esp,ebp ;esp复原
    00411385 pop ebp ;弹出ebp,也复原
    00411386 ret ;返回
    ;函数调用:
    int _tmain(int argc, _TCHAR* argv[])
    {
    ;同上理解, 保存现场
    004113D0 push ebp
    004113D1 mov ebp,esp
    004113D3 sub esp,0F0h ;一共留了0F0h(240)空间
    004113D9 push ebx
    004113DA push esi
    004113DB push edi
    004113DC lea edi,[ebp-0F0h]
    004113E2 mov ecx,3Ch ; ecx = 3C(60),3C*4 = 0F0h,
    004113E7 mov eax,0CCCCCCCCh
    004113EC rep stos dword ptr es:[edi]
    ;同上理解.
    ; int a = 1, b = 2, c = 3;
    ;定义a,b,c并存储在为函数留出的临时存储空间中.
    004113EE mov dword ptr [a],1
    004113F5 mov dword ptr [b],2
    004113FC mov dword ptr [c],3
    ; int d = Fun1(a, b, c);
    ;参数反向入栈
    00411403 mov eax,dword ptr [c]
    00411406 push eax
    00411407 mov ecx,dword ptr [b]
    0041140A push ecx
    0041140B mov edx,dword ptr [a]
    0041140E push edx
    ;调用Fun1
    0041140F call Fun1 (4111DBh) ;Call调用时将下一行命令的EIP压入堆栈
    ;恢复因为Fun1参数入栈改变的栈指针,因为Fun1有3个参数,一个整数4个字节,共0Ch(12)个字节
    00411414 add esp,0Ch
    00411417 mov dword ptr [d],eax
    将返回值保存在d中.
    return 0;
    返回值为0,让eax清零
    0041141A xor eax,eax
    }
    恢复现场
    0041141C pop edi
    0041141D pop esi
    0041141E pop ebx
    以下全为运行时ESP检查:
    先恢复因为为main预留空间而改变的栈指针
    0041141F add esp,0F0h
    00411425 cmp ebp,esp
    00411427 call @ILT+320(__RTC_CheckEsp) (411145h)
    正常时只需要以下两句就可以正常恢复esp,再出栈,又可以恢复ebp.
    0041142C mov esp,ebp
    0041142E pop ebp
    0041142F ret ;main返回
    int Fun1(int a, int b, int c)
    {
    同上理解, 保存现场
    00411A70 push ebp
    00411A71 mov ebp,esp
    00411A73 sub esp,0E4h ;留了0E4H(228)空间,
    00411A79 push ebx
    00411A7A push esi
    00411A7B push edi
    00411A7C lea edi,[ebp-0E4h]
    00411A82 mov ecx,39h ; 39H(57)*4 = 0E4H(228)
    00411A87 mov eax,0CCCCCCCCh
    00411A8C rep stos dword ptr es:[edi]
    int d = 4, e = 5;
    定义变量
    00411A8E mov dword ptr [d],4
    00411A95 mov dword ptr [e],5
    int f = Fun2(a, b, c, d, e);
    再次参数反向入栈
    00411A9C mov eax,dword ptr [e]
    00411A9F push eax
    00411AA0 mov ecx,dword ptr [d]
    00411AA3 push ecx
    00411AA4 mov edx,dword ptr [c]
    00411AA7 push edx
    00411AA8 mov eax,dword ptr [b]
    00411AAB push eax
    00411AAC mov ecx,dword ptr [a]
    00411AAF push ecx
    调用Fun2
    00411AB0 call Fun2 (4111D6h) ;Call调用时将下一行命令的EIP压入堆栈
    00411AB5 add esp,14h ;恢复因为参数入栈改变的栈指针,因为Fun2有5个参数,一个整数4个字节,共14h(20)个字节
    将Fun2函数的返回值(保存在eax中),赋值给f;
    00411AB8 mov dword ptr [f],eax
    return f;
    将保留在f中的Fun1的返回值保存在eax中返回
    00411ABB mov eax,dword ptr [f]
    }
    恢复现场
    00411ABE pop edi
    00411ABF pop esi
    00411AC0 pop ebx
    以下全为运行时ESP检查:
    先恢复因为预留函数存储控件而改变的栈指针,
    00411AC1 add esp,0E4h
    再比较ebp,esp,假如程序运行正确,两个值应该相等.
    00411AC7 cmp ebp,esp
    00411AC9 call @ILT+320(__RTC_CheckEsp) (411145h)
    正常时只需要以下两句就可以正常恢复esp,再出栈,又可以恢复ebp.
    00411ACE mov esp,ebp
    00411AD0 pop ebp
    返回main从pop堆栈中的EIP开始执行
    00411AD1 ret
    int Fun2(int a, int b, int c, int d, int e)
    {
    同上理解, 保存现场
    00412050 push ebp
    00412051 mov ebp,esp
    00412053 sub esp,0E4h ;保留0E4H(228)
    00412059 push ebx
    0041205A push esi
    0041205B push edi
    0041205C lea edi,[ebp-0E4h]
    00412062 mov ecx,39h ; 39H(57)*4 = 0E4H(228)
    00412067 mov eax,0CCCCCCCCh
    0041206C rep stos dword ptr es:[edi]
    int f = 6, g = 7;
    定义变量
    0041206E mov dword ptr [f],6
    00412075 mov dword ptr [g],7
    int h = a + b + c + d + e + f + g;
    相加,存入a,再保存在h
    0041207C mov eax,dword ptr [a]
    0041207F add eax,dword ptr [b]
    00412082 add eax,dword ptr [c]
    00412085 add eax,dword ptr [d]
    00412088 add eax,dword ptr [e]
    0041208B add eax,dword ptr [f]
    0041208E add eax,dword ptr [g]
    00412091 mov dword ptr [h],eax
    return h;
    将返回值h的值保存在eax中
    00412094 mov eax,dword ptr [h]
    }
    恢复现场
    00412097 pop edi
    00412098 pop esi
    00412099 pop ebx
    0041209A mov esp,ebp
    0041209C pop ebp
    0041209D ret ;返回fun1 ,从pop堆栈中的EIP开始执行

    C++中的vptr指针

    C++中的vptr指针

    若类中包含虚函数,则编译器会在类实例化对象时在对象中加入vptr指针,它指向一个虚函数表,子类和父类分别有自己的虚函数表,所以使用父类指针调用类的虚函数时,是根据实际的对象时子类对象还是父类对象,来实现虚函数的调用。

    引入vptr指针

    demo.cpp:

    1. #include <iostream>
    2.  
    3. //多态成立的三个条件
    4. //要有继承 虚函数重写 父类指针指向子类对象
    5. using namespace std;
    6.  
    7. class Parent
    8. {
    9. public:
    10. Parent(int a = 0)
    11. {
    12. this->a = a;
    13. }
    14. virtual void print()//动手脚1,使用virtual关键字,编译器会特殊处理
    15. {
    16. cout << "我是爹" <<endl;
    17. }
    18. private:
    19. int a;
    20. };
    21.  
    22. class Child : public Parent
    23. {
    24. public:
    25. Child(int a = 0, int b = 0):Parent(a)
    26. {
    27. this->b = b;
    28. }
    29. virtual void print()
    30.  
    31. cout << "我是儿子" <<endl;
    32. }
    33. private:
    34. int b;
    35. };
    36.  
    37. void HowToPlay(Parent * base)
    38. {
    39. base->print();//动手脚 2
    40. /*传来子类对象,执行子类的print函数,传来父类对象
    41.   执行父类print函数,其实C++编译器根本不需要区分是子类
    42. 对象还是父类对象,父类对象和子类对象分别有vptr指针,指向
    43. 虚函数表,表中存储函数入口地址,实现迟绑定(运行时,C++程序采取判断)
    44.  
    45. */
    46. }
    47.  
    48. int main()
    49. {
    50. Parent p1(2);//动手脚 3(提前布局)
    51. //用类实例化对象时,C++编译器会在对象中添加一个vptr指针
    52. //vptr指向虚函数表,此表由编译器生成和维护
    53. Child c1;
    54. HowToPlay(&p1);
    55. HowToPlay(&c1);
    56. }

    下面我们来证明vptr指针的存在

    demo2.cpp:

    1. #include <iostream>
    2.  
    3. using namespace std;
    4.  
    5. class Parent
    6. {
    7. public:
    8. virtual void print()
    9. {
    10. cout << "hello" << endl;
    11. }
    12. private:
    13. int a;
    14. };
    15.  
    16. class Parent2
    17. {
    18. public:
    19. void print()
    20. {
    21. cout << "hello" << endl;
    22. }
    23. private:
    24. int a;
    25. };
    26.  
    27. int main()
    28. {
    29. void * p;
    30. cout << "sizeof(Parent):" << sizeof(Parent) << endl;
    31. cout << "sizeof(Parent2):" << sizeof(Parent2) << endl;
    32. }

    输出结果为:

    1. sizeof(Parent):8
    2. sizeof(Parent2):4

    Parent类比Parent2多4个字节,这4个字节就是vptr指针所占的空间。

     

    vptr指针的分步初始化

    demo3.cpp:

    1. #include <iostream>
    2.  
    3. using namespace std;
    4.  
    5. //vptr指针的分步初始化
    6.  
    7. //构造函数中调用虚函数能实现多态吗(不能)
    8.  
    9. //1.要初始化c1.vptr指针,初始化是分步的
    10. //2.当执行父类的构造函数时,c1.vptr指向父类的虚函数表,
    11. // 当父类构造函数执行完毕后,会把c1.vptr指向子类的虚函数表
    12. //3.结论:子类的c1.vptr指针分步完成
    13.  
    14. class Parent
    15. {
    16. public:
    17. Parent(int a = 0)
    18. {
    19. this->a = a;
    20. print();//这里并不能发生多态,调用的仍然是父类的print,
    21. //结果是显而易见的,此时子类还没有构建好,成员变量
    22. //还没初始化,如果调用子类的函数,是不合情理的
    23. }
    24. virtual void print()
    25. {
    26. cout << "我是爹" <<endl;
    27. }
    28. private:
    29. int a;
    30. };
    31.  
    32. class Child : public Parent
    33. {
    34. public:
    35. Child(int a = 0, int b = 0):Parent(a)
    36. {
    37. this->b = b;
    38. print();
    39. }
    40. virtual void print()
    41. {
    42. cout << "我是儿子" <<endl;
    43. }
    44. private:
    45. int b;
    46. };
    47.  
    48.  
    49. int main()
    50. {
    51. Child c1;
    52. }

    C++中的vptr指针

    若类中包含虚函数,则编译器会在类实例化对象时在对象中加入vptr指针,它指向一个虚函数表,子类和父类分别有自己的虚函数表,所以使用父类指针调用类的虚函数时,是根据实际的对象时子类对象还是父类对象,来实现虚函数的调用。

    引入vptr指针

    demo.cpp:

    1.  
      #include <iostream>
    2.  
       
    3.  
      //多态成立的三个条件
    4.  
      //要有继承 虚函数重写 父类指针指向子类对象
    5.  
      using namespace std;
    6.  
       
    7.  
      class Parent
    8.  
      {
    9.  
      public:
    10.  
      Parent(int a = 0)
    11.  
      {
    12.  
      this->a = a;
    13.  
      }
    14.  
      virtual void print()//动手脚1,使用virtual关键字,编译器会特殊处理
    15.  
      {
    16.  
      cout << "我是爹" <<endl;
    17.  
      }
    18.  
      private:
    19.  
      int a;
    20.  
      };
    21.  
       
    22.  
      class Child : public Parent
    23.  
      {
    24.  
      public:
    25.  
      Child(int a = 0, int b = 0):Parent(a)
    26.  
      {
    27.  
      this->b = b;
    28.  
      }
    29.  
      virtual void print()
    30.  
       
    31.  
      cout << "我是儿子" <<endl;
    32.  
      }
    33.  
      private:
    34.  
      int b;
    35.  
      };
    36.  
       
    37.  
      void HowToPlay(Parent * base)
    38.  
      {
    39.  
      base->print();//动手脚 2
    40.  
      /*传来子类对象,执行子类的print函数,传来父类对象
    41.  
        执行父类print函数,其实C++编译器根本不需要区分是子类
    42.  
      对象还是父类对象,父类对象和子类对象分别有vptr指针,指向
    43.  
      虚函数表,表中存储函数入口地址,实现迟绑定(运行时,C++程序采取判断)
    44.  
       
    45.  
      */
    46.  
      }
    47.  
       
    48.  
      int main()
    49.  
      {
    50.  
      Parent p1(2);//动手脚 3(提前布局)
    51.  
      //用类实例化对象时,C++编译器会在对象中添加一个vptr指针
    52.  
      //vptr指向虚函数表,此表由编译器生成和维护
    53.  
      Child c1;
    54.  
      HowToPlay(&p1);
    55.  
      HowToPlay(&c1);
    56.  
      }

    下面我们来证明vptr指针的存在

    demo2.cpp:

    1.  
      #include <iostream>
    2.  
       
    3.  
      using namespace std;
    4.  
       
    5.  
      class Parent
    6.  
      {
    7.  
      public:
    8.  
      virtual void print()
    9.  
      {
    10.  
      cout << "hello" << endl;
    11.  
      }
    12.  
      private:
    13.  
      int a;
    14.  
      };
    15.  
       
    16.  
      class Parent2
    17.  
      {
    18.  
      public:
    19.  
      void print()
    20.  
      {
    21.  
      cout << "hello" << endl;
    22.  
      }
    23.  
      private:
    24.  
      int a;
    25.  
      };
    26.  
       
    27.  
      int main()
    28.  
      {
    29.  
      void * p;
    30.  
      cout << "sizeof(Parent):" << sizeof(Parent) << endl;
    31.  
      cout << "sizeof(Parent2):" << sizeof(Parent2) << endl;
    32.  
      }

    输出结果为:

    1.  
      sizeof(Parent):8
    2.  
      sizeof(Parent2):4

    Parent类比Parent2多4个字节,这4个字节就是vptr指针所占的空间。

     

    vptr指针的分步初始化

    demo3.cpp:

    1.  
      #include <iostream>
    2.  
       
    3.  
      using namespace std;
    4.  
       
    5.  
      //vptr指针的分步初始化
    6.  
       
    7.  
      //构造函数中调用虚函数能实现多态吗(不能)
    8.  
       
    9.  
      //1.要初始化c1.vptr指针,初始化是分步的
    10.  
      //2.当执行父类的构造函数时,c1.vptr指向父类的虚函数表,
    11.  
      // 当父类构造函数执行完毕后,会把c1.vptr指向子类的虚函数表
    12.  
      //3.结论:子类的c1.vptr指针分步完成
    13.  
       
    14.  
      class Parent
    15.  
      {
    16.  
      public:
    17.  
      Parent(int a = 0)
    18.  
      {
    19.  
      this->a = a;
    20.  
      print();//这里并不能发生多态,调用的仍然是父类的print,
    21.  
      //结果是显而易见的,此时子类还没有构建好,成员变量
    22.  
      //还没初始化,如果调用子类的函数,是不合情理的
    23.  
      }
    24.  
      virtual void print()
    25.  
      {
    26.  
      cout << "我是爹" <<endl;
    27.  
      }
    28.  
      private:
    29.  
      int a;
    30.  
      };
    31.  
       
    32.  
      class Child : public Parent
    33.  
      {
    34.  
      public:
    35.  
      Child(int a = 0, int b = 0):Parent(a)
    36.  
      {
    37.  
      this->b = b;
    38.  
      print();
    39.  
      }
    40.  
      virtual void print()
    41.  
      {
    42.  
      cout << "我是儿子" <<endl;
    43.  
      }
    44.  
      private:
    45.  
      int b;
    46.  
      };
    47.  
       
    48.  
       
    49.  
      int main()
    50.  
      {
    51.  
      Child c1;
    52.  
      }
  • 相关阅读:
    利用HttpClient进行带参数的http文件上传
    使用mysqlproxy 快速实现mysql 集群 读写分离 [转]
    SQL Server Express 自动备份方法
    screen 配置文件
    linux启动DHCP
    dynamic table_name in cursor
    LogMiner and supplemental logging
    RAC 规划配置网络环境
    RAC prepare OS and installation media
    一次导数据流程
  • 原文地址:https://www.cnblogs.com/Chary/p/13609307.html
Copyright © 2020-2023  润新知