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

很多朋友,包括我自己在内,对C++语言的很多特性不是很明白。特别是几年前找工作的时候,为了应付来自工作单位的考试,我经常逼着自己的去记住一些复杂的试题和答案。可是常常时间已过,一切又回到了原点。原来没有弄清楚的问题还是没有弄明白,一切都没有发生改变。直到若干年后,当我在编码过程中不断积累经验,尝试用汇编代码和内存数据来解释一些现象的时候,才明白有些东西其实并不复杂。也许有的朋友对汇编语言会有畏惧,其实没有必要。只要你对C语言有一些基础,对堆栈有一些印象,那么你已经拥有汇编语言的基础了。在接下来的数篇博客中,我们就会就x86汇编、数据类型、数据运行逻辑、指针、数据、类、重载运算符在汇编下是如何展开的做一些介绍,谈一些个人的看法。下面,我们就进行一些小测试,同时用汇编语言来说明一下。大家可以一起做一下。

(1) char name[] 和 char* name

1:
2:    void process()
3:    {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,4Ch
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-4Ch]
0040102C   mov         ecx,13h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
4:        char name_tmp[] = {"hello"};
00401038   mov         eax,[string "hello" (0042201c)]
0040103D   mov         dword ptr [ebp-8],eax
00401040   mov         cx,word ptr [string "hello"+4 (00422020)]
00401047   mov         word ptr [ebp-4],cx
5:        char* name_glb = "hello";
0040104B   mov         dword ptr [ebp-0Ch],offset string "hello" (0042201c)
6:    }
00401052   pop         edi
00401053   pop         esi
00401054   pop         ebx
00401055   mov         esp,ebp
00401057   pop         ebp
00401058   ret

通过上面的代码,我们可以清楚地看出两者之间的差别。"hello"字符串是一个全局只读变量,空间地址为0x0042201C。name_tmp是函数内的char数组,第4行语句下面四行表示全局数据“hello”是分两次拷贝到name_tmp的,第一次是dword、四个字节,第二次是word、两个字节。所以name_tmp共有6个字节。相比较而言,name_glb什么也没有,它只是把自己指向了全局变量而已,所以它只是一个指针而已。

(2)apple a()和apple b

假设class apple的定义为:

class apple
{
public:apple() {}~apple() {}
};

那么apple a()和apple b是分别怎么编译的呢?

9:    void process()
10:   {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,44h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-44h]
0040102C   mov         ecx,11h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
11:       apple a();
12:       apple b;
00401038   lea         ecx,[ebp-4]
0040103B   call        @ILT+20(apple::apple) (00401019)
13:   }
00401040   lea         ecx,[ebp-4]
00401043   call        @ILT+10(apple::~apple) (0040100f)
00401048   pop         edi
00401049   pop         esi
0040104A   pop         ebx
0040104B   add         esp,44h
0040104E   cmp         ebp,esp
00401050   call        __chkesp (004010b0)
00401055   mov         esp,ebp
00401057   pop         ebp
00401058   ret

为什么apple a()这边什么也没有编译呢?原因很简单,因为编译器把apple a()看成是一个extern的函数,返回值为apple。与此相对应的apple b才是函数中真正定义的临时变量,因为在下面不远处有apple的两个函数——apple的构造函数和apple的析构函数哦。

(3)(apple*) (0) -> print()

其中class apple这样定义:

class apple
{int value;
public:apple() {}~apple() {}void print() { return;}
};

如果0设置为apple*,那么访问函数print会有问题吗?

10:   void process()
11:   {
00401030   push        ebp
00401031   mov         ebp,esp
00401033   sub         esp,40h
00401036   push        ebx
00401037   push        esi
00401038   push        edi
00401039   lea         edi,[ebp-40h]
0040103C   mov         ecx,10h
00401041   mov         eax,0CCCCCCCCh
00401046   rep stos    dword ptr [edi]
12:       ((apple*)(0))->print();
00401048   xor         ecx,ecx
0040104A   call        @ILT+0(apple::print) (00401005)
13:   }
0040104F   pop         edi
00401050   pop         esi
00401051   pop         ebx
00401052   add         esp,40h
00401055   cmp         ebp,esp
00401057   call        __chkesp (004010e0)
0040105C   mov         esp,ebp
0040105E   pop         ebp
0040105F   ret

通过运行函数,我们发现没有任何异常产生,为什么呢?因为我们发现ecx是作为0传给print函数的,也就是我们熟悉的this指针为0。但是我们发现在print函数内部没有用到this指针,因为我们根本没有对this->value进行访问,只是一个返回语句return。这说明指针作为class null指针并不可怕,可怕的是用null去访问内存中的数据。

(4) int m = 1; int n = m++ + ++m; 那么n是多少呢?

10:   void process()
11:   {
0040D4D0   push        ebp
0040D4D1   mov         ebp,esp
0040D4D3   sub         esp,48h
0040D4D6   push        ebx
0040D4D7   push        esi
0040D4D8   push        edi
0040D4D9   lea         edi,[ebp-48h]
0040D4DC   mov         ecx,12h
0040D4E1   mov         eax,0CCCCCCCCh
0040D4E6   rep stos    dword ptr [edi]
12:       int m = 1;
0040D4E8   mov         dword ptr [ebp-4],1
13:       int n = m++ + ++m;
0040D4EF   mov         eax,dword ptr [ebp-4]
0040D4F2   add         eax,1
0040D4F5   mov         dword ptr [ebp-4],eax
0040D4F8   mov         ecx,dword ptr [ebp-4]
0040D4FB   add         ecx,dword ptr [ebp-4]
0040D4FE   mov         dword ptr [ebp-8],ecx
0040D501   mov         edx,dword ptr [ebp-4]
0040D504   add         edx,1
0040D507   mov         dword ptr [ebp-4],edx
14:   }
0040D50A   pop         edi
0040D50B   pop         esi
0040D50C   pop         ebx
0040D50D   mov         esp,ebp
0040D50F   pop         ebp

通过汇编代码,我们看到【ebp-4】就是m在堆栈中的地址,【ebp-8】就是n在堆栈中的地址。 int n = m++ + ++m下面总共有9句汇编。我们可以分析一下:前面三句表示m自己增加1,第四句表示ecx = m,即ecx = 2。第五句ecx和m相加,翻译过来就是ecx = ecx + m。此时ecx = 4。第六句表示 n = ecx。 第七句到第九句表示m自增加1。为什么会出现这样的情况呢,其实道理很简单,主要是因为我们的表达式是从右向左运算的。如果大家这样看就明白了,首先++m,然后 n = m + m,最后 m++。

(5) *p++和(*p)++区别是什么

10:   void process()
11:   {
0040D4D0   push        ebp
0040D4D1   mov         ebp,esp
0040D4D3   sub         esp,48h
0040D4D6   push        ebx
0040D4D7   push        esi
0040D4D8   push        edi
0040D4D9   lea         edi,[ebp-48h]
0040D4DC   mov         ecx,12h
0040D4E1   mov         eax,0CCCCCCCCh
0040D4E6   rep stos    dword ptr [edi]
12:       char data = 'a';
0040D4E8   mov         byte ptr [ebp-4],61h
13:       char* p = & data;
0040D4EC   lea         eax,[ebp-4]
0040D4EF   mov         dword ptr [ebp-8],eax
14:       *p++;
0040D4F2   mov         ecx,dword ptr [ebp-8]
0040D4F5   add         ecx,1
0040D4F8   mov         dword ptr [ebp-8],ecx
15:       (*p)++;
0040D4FB   mov         edx,dword ptr [ebp-8]
0040D4FE   mov         al,byte ptr [edx]
0040D500   add         al,1
0040D502   mov         ecx,dword ptr [ebp-8]
0040D505   mov         byte ptr [ecx],al
16:   }
0040D507   pop         edi
0040D508   pop         esi
0040D509   pop         ebx
0040D50A   mov         esp,ebp
0040D50C   pop         ebp
0040D50D   ret

我们首先创建局部变量data。然后把data的指针复制给p。从汇编代码可以清楚的看出来:*p++就等于p++;(*p)++首先把指针复制给edx,然后获取edx地址指向的char数据复制给al,al自增加1,同时p地址复制给ecx,al复制给ecx指向的地址,就是这么简单。

类似的问题还有很多,大家不妨自己试一试:

(1) 下面的union在内存是怎么安排的?gcc和vc编译的时候,分配的内存size是一样的吗?

typedef union
{
char m:3;
char n:7;
int data;
}value;

(2) 下面地址一致吗?

char value1[] = {"hello"};
char value2[] = {"hello"};
char* pValue1 = “hello”;
char* pValue2 = "hello";
value1和value2地址一致吗?pValue1和pValue2呢?

(3)下面一段话为什么运行错误?为什么内存泄露了?怎么修改?

class apple
{char* pName;
public:apple() { pName = (char*)malloc(10);}~apple() {if(NULL != pName) free(pName);}
};void process()
{apple a, b;a = b;
}

(全文完)

用汇编的眼光看C++(开篇)相关推荐

  1. 用汇编的眼光看C++(之 总结篇)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 早在八月份的时候,就陆陆续续写了二十多篇用汇编语言看C++的博客内容.在此为了做一个概括,也为 ...

  2. 用汇编的眼光看C++(之退出流程)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 无论是在判断还是在循环的过程中,通常在遇到合适的条件的时候就会退出相应的模块.跳出模块运行的方 ...

  3. 用汇编的眼光看C++ (之x86汇编)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 说到用汇编的眼光看C++语言,那么怎么阅读汇编代码就成了我们需要解决的一个问题.其实,实话说, ...

  4. 用汇编的眼光看C++(之判断流程)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 在我们平常的编程当中,用于判断的地方很多,但主要有下面三种方式:if-else:switch: ...

  5. 用汇编的眼光看C++(之拷贝、赋值函数)

    拷贝构造函数和复制函数是类里面比较重要的两个函数.两者有什么区别呢?其实也很简单,我们可以举个例子,加入有这样一个类的定义: [cpp] view plaincopy class apple { pu ...

  6. 用汇编的眼光看C++(之算术符重载陷阱)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 在算术符重载里面,"="重载可能是最经常使用的一种.但是好多人就误以为在函 ...

  7. 从汇编的眼光看C++(之指针拷贝)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 指针是编程人员的梦魇,对C语言的开发者是如此,对C++的开发者也是如此.特别是在C++中,如果 ...

  8. 从汇编的眼光看C++(之递归函数与模板类)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 递归,相信有过基本C语言经验的朋友都明白,就是函数自己调用自己.所以,本质上说,它和普通的函数 ...

  9. 用汇编的眼光看C++(之模板类)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 如果类是一种确定的数据类型,那么模板就是一种对类的抽象.假设有这么一种类,它需要进行数据的计算 ...

最新文章

  1. 计算机二级公共基础知识证书,计算机二级公共基础知识
  2. 提高抗打击能力_输不起、爱放弃,孩子抗挫能力差怎么办?3招教你培养孩子抗挫力...
  3. python中title用法_在CSV fi中使用Title()
  4. Cocos2dx-如何利用NDK分析崩溃日志
  5. openresty完全开发指南_FDA拟修订群体药代动力学指南:医药商需要了解些什么?...
  6. Oracle的解惑一二to date 与24小时制表示法及mm分钟的显示
  7. 相对完善的Java通过JDBC操纵mysql的例子
  8. PHP CI框架如何去掉 sql 里的反引号
  9. 矩池云升级JupyterLab版本教程
  10. 展示魅力东莞,传递亚运激情
  11. schedule和scheduleAtFixedRate
  12. svn服务器端 忽略文件夹,SVN忽略不需要版本控制的文件或者文件夹
  13. 矩阵特征值和特征向量详细计算过程
  14. 宫颈癌风险的智能诊断
  15. UUID订单单号生成器
  16. 浙江中医药大学第十二届大学生程序设计竞赛 部分题解
  17. 在vue.config.js下配置别名alias
  18. 电脑远程开机控制实现 免拆机安装
  19. 教授专栏08| 徐岩:青年震荡与青年危机
  20. vimPlus插件安装失败解决

热门文章

  1. 给apm换一个软件源
  2. 为了更准确的验证码而奋斗-云大urp教务系统大作战(1.5)
  3. 手把手教你在友善之臂tiny4412上用uboot启动Linux内核
  4. 写给自己,梳理一下我现在对前端知识结构的理解
  5. ios 判断某个时间是周几的方法
  6. Java Swing创建自定义闪屏:在闪屏上添加Swing进度条控件(转)
  7. 引入css外部样式表的注意事项
  8. Centos 6.2出现Disk sda contains BIOS RAID metadata解决方法
  9. (一)JAVA 点滴积累之JAVA开发环境安装
  10. 剑指Offer——和为s的两个数字