用汇编的眼光看C++(开篇)

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

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

(1) char name[] 和 char nameview plain
1. 1:
2. 2:voidprocess()
3. 3: {
4. 00401020 push ebp
5. 00401021 mov ebp,esp
6. 00401023 sub esp,4Ch
7. 00401026 push ebx
8. 00401027 push esi
9. 00401028 push edi
10. 00401029 lea edi,[ebp-4Ch]
11. 0040102C mov ecx,13h
12. 00401031 mov eax,0CCCCCCCCh
13. 00401036 rep stos dword ptr [edi]
14. 4:charname_tmp[] = {"hello"};
15. 00401038 mov eax,[string"hello"(0042201c)]
16. 0040103D mov dword ptr [ebp-8],eax
17. 00401040 mov cx,word ptr [string"hello"+4 (00422020)]
18. 00401047 mov word ptr [ebp-4],cx
19. 5:char
name_glb ="hello";
20. 0040104B mov dword ptr [ebp-0Ch],offset string"hello"(0042201c)
21. 6: }
22. 00401052 pop edi
23. 00401053 pop esi
24. 00401054 pop ebx
25. 00401055 mov esp,ebp
26. 00401057 pop ebp
27. 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的定义为:view plain
1. classapple
2. {
3. public:
4. apple() {}
5. ~apple() {}
6. };
那么apple a()和apple b是分别怎么编译的呢?view plain
1. 9:voidprocess()
2. 10: {
3. 00401020 push ebp
4. 00401021 mov ebp,esp
5. 00401023 sub esp,44h
6. 00401026 push ebx
7. 00401027 push esi
8. 00401028 push edi
9. 00401029 lea edi,[ebp-44h]
10. 0040102C mov ecx,11h
11. 00401031 mov eax,0CCCCCCCCh
12. 00401036 rep stos dword ptr [edi]
13. 11: apple a();
14. 12: apple b;
15. 00401038 lea ecx,[ebp-4]
16. 0040103B call @ILT+20(apple::apple) (00401019)
17. 13: }
18. 00401040 lea ecx,[ebp-4]
19. 00401043 call @ILT+10(apple::~apple) (0040100f)
20. 00401048 pop edi
21. 00401049 pop esi
22. 0040104A pop ebx
23. 0040104B add esp,44h
24. 0040104E cmp ebp,esp
25. 00401050 call __chkesp (004010b0)
26. 00401055 mov esp,ebp
27. 00401057 pop ebp
28. 00401058 ret


为什么apple a()这边什么也没有编译呢?原因很简单,因为编译器把apple a()看成是一个extern的函数,返回值为apple。与此相对应的apple b才是函数中真正定义的临时变量,因为在下面不远处有apple的两个函数——apple的构造函数和apple的析构函数哦。(3)(apple*) (0) -> print()其中class apple这样定义:view plain
1. classapple
2. {
3. intvalue;
4. public:
5. apple() {}
6. ~apple() {}
7. voidprint() {return;}
8. };
如果0设置为apple,那么访问函数print会有问题吗?view plain
1. 10:voidprocess()
2. 11: {
3. 00401030 push ebp
4. 00401031 mov ebp,esp
5. 00401033 sub esp,40h
6. 00401036 push ebx
7. 00401037 push esi
8. 00401038 push edi
9. 00401039 lea edi,[ebp-40h]
10. 0040103C mov ecx,10h
11. 00401041 mov eax,0CCCCCCCCh
12. 00401046 rep stos dword ptr [edi]
13. 12: ((apple
)(0))->print();
14. 00401048 xor ecx,ecx
15. 0040104A call @ILT+0(apple::print) (00401005)
16. 13: }
17. 0040104F pop edi
18. 00401050 pop esi
19. 00401051 pop ebx
20. 00401052 add esp,40h
21. 00401055 cmp ebp,esp
22. 00401057 call __chkesp (004010e0)
23. 0040105C mov esp,ebp
24. 0040105E pop ebp
25. 0040105F ret
通过运行函数,我们发现没有任何异常产生,为什么呢?因为我们发现ecx是作为0传给print函数的,也就是我们熟悉的this指针为0。但是我们发现在print函数内部没有用到this指针,因为我们根本没有对this->value进行访问,只是一个返回语句return。这说明指针作为class null指针并不可怕,可怕的是用null去访问内存中的数据。 (4) int m = 1; int n = m++ + ++m; 那么n是多少呢?view plain
1. 10:voidprocess()
2. 11: {
3. 0040D4D0 push ebp
4. 0040D4D1 mov ebp,esp
5. 0040D4D3 sub esp,48h
6. 0040D4D6 push ebx
7. 0040D4D7 push esi
8. 0040D4D8 push edi
9. 0040D4D9 lea edi,[ebp-48h]
10. 0040D4DC mov ecx,12h
11. 0040D4E1 mov eax,0CCCCCCCCh
12. 0040D4E6 rep stos dword ptr [edi]
13. 12:intm = 1;
14. 0040D4E8 mov dword ptr [ebp-4],1
15. 13:intn = m++ + ++m;
16. 0040D4EF mov eax,dword ptr [ebp-4]
17. 0040D4F2 add eax,1
18. 0040D4F5 mov dword ptr [ebp-4],eax
19. 0040D4F8 mov ecx,dword ptr [ebp-4]
20. 0040D4FB add ecx,dword ptr [ebp-4]
21. 0040D4FE mov dword ptr [ebp-8],ecx
22. 0040D501 mov edx,dword ptr [ebp-4]
23. 0040D504 add edx,1
24. 0040D507 mov dword ptr [ebp-4],edx
25. 14: }
26. 0040D50A pop edi
27. 0040D50B pop esi
28. 0040D50C pop ebx
29. 0040D50D mov esp,ebp
30. 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)++区别是什么view plain
1. 10:voidprocess()
2. 11: {
3. 0040D4D0 push ebp
4. 0040D4D1 mov ebp,esp
5. 0040D4D3 sub esp,48h
6. 0040D4D6 push ebx
7. 0040D4D7 push esi
8. 0040D4D8 push edi
9. 0040D4D9 lea edi,[ebp-48h]
10. 0040D4DC mov ecx,12h
11. 0040D4E1 mov eax,0CCCCCCCCh
12. 0040D4E6 rep stos dword ptr [edi]
13. 12:chardata ='a';
14. 0040D4E8 mov byte ptr [ebp-4],61h
15. 13:char p = & data;
16. 0040D4EC lea eax,[ebp-4]
17. 0040D4EF mov dword ptr [ebp-8],eax
18. 14:
p++;
19. 0040D4F2 mov ecx,dword ptr [ebp-8]
20. 0040D4F5 add ecx,1
21. 0040D4F8 mov dword ptr [ebp-8],ecx
22. 15: (p)++;
23. 0040D4FB mov edx,dword ptr [ebp-8]
24. 0040D4FE mov al,byte ptr [edx]
25. 0040D500 add al,1
26. 0040D502 mov ecx,dword ptr [ebp-8]
27. 0040D505 mov byte ptr [ecx],al
28. 16: }
29. 0040D507 pop edi
30. 0040D508 pop esi
31. 0040D509 pop ebx
32. 0040D50A mov esp,ebp
33. 0040D50C pop ebp
34. 0040D50D ret
我们首先创建局部变量data。然后把data的指针复制给p。从汇编代码可以清楚的看出来:
p++就等于p++;(p)++首先把指针复制给edx,然后获取edx地址指向的char数据复制给al,al自增加1,同时p地址复制给ecx,al复制给ecx指向的地址,就是这么简单。

类似的问题还有很多,大家不妨自己试一试:
(1) 下面的union在内存是怎么安排的?gcc和vc编译的时候,分配的内存size是一样的吗?view plain
1. typedefunion
2. {
3. charm:3;
4. charn:7;
5. intdata;
6. }value;
(2) 下面地址一致吗?view plain
1. charvalue1[] = {"hello"};
2. charvalue2[] = {"hello"};
3. char
pValue1 = “hello”;
4. char pValue2 ="hello";
5. value1和value2地址一致吗?pValue1和pValue2呢?
(3)下面一段话为什么运行错误?为什么内存泄露了?怎么修改?view plain
1. classapple
2. {
3. char
pName;
4. public:
5. apple() { pName = (char)malloc(10);}
6. ~apple() {if(NULL != pName) free(pName);}
7. };
8.
9. voidprocess()
10. {
11. apple a, b;
12. a = b;
13. }


(全文完)


原文链接: https://www.cnblogs.com/sier/archive/2011/09/21/5676480.html
欢迎关注*

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/32769

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月8日 上午9:53
下一篇 2023年2月8日 上午9:55

相关推荐