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. }
  53.  

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.  
    }
posted @ 2020-09-03 18:56  CharyGao  阅读(654)  评论(0编辑  收藏  举报