用两个程序帮助理解返回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。