通过汇编理解返回char p []和char *p 中P的区别

来源:互联网 发布:dota2 mac 国服下载 编辑:程序博客网 时间:2024/06/05 13:30

用两个程序帮助理解返回char p []和char *p 中p的区别。

第一个程序test01.cpp的源代码:

/* *filename:test01.cpp */#include <stdio.h>   char *returnStr(){char *p = "hello world!";return p;}int main(){char *str;str = returnStr();printf("%s\n", str);return 0;}

test001.cpp的运行结果:hello world!


第二个程序test002.cpp的源代码

/* *filename:test02.cpp */#include <stdio.h>   char *returnStr(){char  p[] = "hello world!";return p;}int main(){char *str;str = returnStr();printf("%s\n", str);return 0;}

返回的结果是乱码的:烫烫烫烫烫烫烫烫,


两个程序代码上的区别在于:

test01.cpp的是

char *p = "hello world!";

test02.cpp的是:

char p[] = "hello world!";

网上解释的是:

test01.cpp程序返回的是指向常量区的地址。程序返回后,常量区地址的内容未变,所以能正常返回“hello world!”

test02.cpp程序返回的是指向的是栈空间的地址,函数返回后,栈空间被释放,返回的地址所指的内容不确定,所以会出现此问题。


以上的解释是能和结果吻合的,但是心中还是有个疑问,等号右边都是一样的“hell world!”,为什么char *p中的p为常量区的地址,而char p[ ]中的p为栈区的地址。于是vs 弄起,看下看下两个程序的关键部分的汇编(前不久自学的汇编,菜鸟一个)。

先看下test02.cpp部分代码的汇编。

char  p[] = "hello world!";return p;
上面两行代码通过vs反汇编看到的汇编代码:
      7: char  p[] = "hello world!";00073D88 A1 30 6B 07 00       mov         eax,dword ptr ds:[00076B30h]  00073D8D 89 45 E8             mov         dword ptr [ebp-18h],eax  00073D90 8B 0D 34 6B 07 00    mov         ecx,dword ptr ds:[00076B34h]  00073D96 89 4D EC             mov         dword ptr [ebp-14h],ecx  00073D99 8B 15 38 6B 07 00    mov         edx,dword ptr ds:[00076B38h]  00073D9F 89 55 F0             mov         dword ptr [ebp-10h],edx  00073DA2 A0 3C 6B 07 00       mov         al,byte ptr ds:[00076B3Ch]  00073DA7 88 45 F4             mov         byte ptr [ebp-0Ch],al       8: return p;00073DAA 8D 45 E8             lea         eax,[ebp-18h]       9: }
从上面看可知,编译器把ds:[00076B30h](备注:可通过vs查看到此时ds寄存器的值为0)的13个字节(加上最后的‘‘\0’)先通过复制到寄存器(一共分4次复制,因为eax、ecx、edx均为4个字节大小,最后一个al为1个字节大小),然后再把寄存器的值复到[ebp-18h](可通过vs查看到此时EBP = 0055F998)地址开始的栈空间(从[ebp-18h]到[ebp-0Ch]  正好是13个字节的空间)。

我们可以看下ds:[00076B30h]内存地址开始的内容:



再看下[ebp-18h]内存地址的空间:



从上面的内容可以看出,同样是“helloword”字符串,他们的地址是不一样的。也就是说,下面这段代码:

char  p[] = "hello world!";return p;
编译器会重新开辟一个栈空间,把数据的段的“hello world!”复制到栈空间,然后再把这段内容的栈空间的起始地址返回。


再看下test01.cpp关键代码:

char  *p = "hello world!";return p;

其对应的汇编

     7: char  *p = "hello world!";00DB170E C7 45 F8 30 6B DB 00 mov         dword ptr [ebp-8],0DB6B30h       8: return p;00DB1715 8B 45 F8             mov         eax,dword ptr [ebp-8]       9: }
0DB6B30h就是存储“hello world!”的数据段起始地址。



也就是说把数据段的地址复制到一个16字节的内存空间(虽然也是栈空间,因为指针变量p占用的是栈空间),再把这段栈空间的值(其实就是数据段地址)复制给eax。没有重新开辟13个栈空间去存储“hello world!”字符串,只是存储地址而已。所以返回的直接就是常量区“hello world!”的地址。

总结:

char  *p = "hello world!";return p;
以上的第一行代码不会重新开辟栈空间去存储字符串"hello world!",开辟的栈空间是为了存储常量区"hello world!"的起始地址,返回的也是常量区"hello world!"的起始地址p。

char  p[] = "hello world!";return p;

以上的第一行代码会开辟栈空间去存储字符串"hello world!",返回的是在栈中存储这段字符串的起始地址,即返回的栈空间的地址p