原帖地址:http://topic.csdn.net/u/20110302/22/a4df98e6-a15c-41d9-8ae2-b24964fc5ab8.html
这篇文章是探索一些C++编译的知识,内容比较枯燥,适宜真正的程序员来看,并且不能太浮躁,否则,是吸收不了这篇文章的营养的,就不要浪费时间继续往下看了。当然,鉴于笔者水平有限,这篇文章的分析也可能有某些地方有错误,恳请大家留言指教。
附:测试环境 Windows Xp ; VS2008
奇怪的代码如下:
#include <iostream>
using namespace std;
char* strcpy_(char* strDest,const char* strSrc)
{
if (strDest==NULL || strSrc == NULL)
{
return 0;
}
char *strDestCopy = strDest;
while ((*strDest++ = *strSrc++) != '/0')
{
;
}
cout<<strSrc<<endl;// adf
cout<<strDestCopy<<endl;// 123
return strDestCopy;
}
int main()
{
char a[10]="adf";
strcpy_(a,"123");
return 0;
}
说明如下:
这段代码的目的是自己写一个字符串拷贝函数,事实上,也完成了这个功能。
奇怪的地方:
在字符串拷贝函数内部,加了两行输出语句,如下:
cout<<strSrc<<endl;
cout<<strDestCopy<<endl;
在看下文之前,试着猜一下这里的输出结果吧,如果你能很明确的说出结果和原因,那么,足以说明,你是一个学习非常深入的程序员,喜欢探索,遇到问题,必找到本质的原因,这一行还是很适合你的。
正常分析认为:
strSrc经过递增操作,已经从指向常量字符串"123”变更为原strSrc地址后的第4个字符,如果此时输出strSrc的内容,结果应该是不可预期的。
strDestCopy仍指向原strDest的起始位置,而strDest刚刚经过赋值,所以此时输出内容应该为赋值后的值,即字符串“123”。
实验验证:
(1)Debug版本
无论如何运行,运行结果总是输出如下:
adf
123
让人奇怪的是,应该为不可预期的strSrc却总是显示输出为“adf”,真是让人觉得非常奇怪。
(2)Release版本
**(不可预期,每次结果都是一个随机的字符串)
123
很明显,Release版本是符合预期的,但是Debug版本却一反常态,与预期不一样,为什么呢?
笔者也觉得很奇怪,为什么呢???
首先让我们一道来看看这个程序的汇编源码::(希望本文的读者也有着一定的汇编基础,否则可能比较难以理解)
Debug模式的主程序的汇编源码如下 :
char a[10] = "adf";
00401118 mov eax,dword ptr [string "adf" (404240h)]
0040111D mov dword ptr [ebp-14h],eax
//上面两行汇编代码已经将静态存储区中的字符串“adf”拷贝入地址a为首的内存中,其实就是栈
00401120 xor eax,eax
00401122 mov dword ptr [ebp-10h],eax
00401125 mov word ptr [ebp-0Ch],ax
strcpy_(a, "123");
00401129 push offset string "123" (40423Ch)
0040112E lea eax,[ebp-14h]
00401131 push eax
00401132 call strcpy_ (401000h)
00401137 add esp,8
return 0;
0040113A xor eax,eax
从这段代码中,我们很轻易的就看出了常量字符串“adf”的地址为0x00404240,而常量字符串“123”的地址为0x0040423C,他们都是存储在静态存储区中的,虽然字符串“adf”已经被拷贝到栈中,但是静态存储区中的内存也并未释放。很明显,如果一个指针指向常量字符串“123”,经过递增操作,那么这个指针的值必然是0x0040423C+4,也就是常量字符串“adf”的地址0x00404240,所以,从这里,我们就清楚为什么Debug版本的输出是那样的了。
进入内存查看窗口,验证一下:
0x0040423C 31 32 33 00 61 64 66 00 00 00 00 00 66 00 3a 00 5c 00 64 00 64 123.adf.....f.:./.d.d
0x00404251 00 5c 00 76 00 63 00 74 00 6f 00 6f 00 6c 00 73 00 5c 00 63 00 ./.v.c.t.o.o.l.s./.c.
0x00404266 72 00 74 00 5f 00 62 00 6c 00 64 00 5c 00 73 00 65 00 6c 00 66 r.t._.b.l.d./.s.e.l.f
0x0040427B 00 5f 00 78 00 38 00 36 00 5c 00 63 00 72 00 74 00 5c 00 73 00 ._.x.8.6./.c.r.t./.s.
那么为什么会是这样的情况呢?因为常量字符串的分配在静态存储区中,在程序启动时,这些需要定义的常量字符串其实已经在静态存储区中按照一定的规则被分配了内存,而且这些常量字符串不会随着程序的关闭被释放,一般直到下一次被覆盖为止,否则一般不会进行释放,所以,在Debug模式下运行这个程序,输出就是那样一个奇怪的结果了。
好了,我们接着分析一下Release模式:
Release模式的主程序的汇编源码如下:
char a[10] = "adf";
0040106E xor eax,eax
strcpy_(a, "123");
00401070 lea ecx,[esp]
00401073 mov dword ptr [esp],666461h //在寄存器中定义了字符串"adf",这个字符串的ascII码即:0x66 0x64 0x61,并将这个字符串拷贝入以a为首地址的内存中,其实就是栈,esp是栈顶指针,默认段基址为ss
0040107A mov dword ptr [esp+4],eax //给上述字符串补充结尾的0字符
0040107E mov word ptr [esp+8],ax
00401083 call strcpy_ (401000h)
return 0;
从上述汇编代码中,我们可以看出,由于是Release模式,编译器做了一些优化,并没有将这些字符串常量定义到静态存储区中,再将静态存储区中的字符串拷贝进首地址为a的内存中,而是直接将其这些字符串常量放到CPU的寄存器中,然后就进行拷贝,放到首地址为a的内存中。也就是说,整个过程并未让静态存储区参与。
那么,此时我们再往下看,跟踪进入函数:strcpy_,相关汇编源码如下:
char* strcpy_(char* strDest,const char* strSrc)
{
if (strDest==NULL || strSrc == NULL)
{
return 0;
}
char *strDestCopy = strDest;
0040100A mov esi,ecx
0040100C lea esp,[esp]
while ((*strDest++ = *strSrc++) != '/0')
00401010 mov al,byte ptr [edx]
00401012 mov byte ptr [ecx],al
00401014 inc ecx
00401015 inc edx
00401016 test al,al
00401018 jne strcpy_+10h (401010h)
{
;
}
cout<<strSrc<<endl;// adf
0040101A mov eax,dword ptr [__imp_std::endl (402038h)]
0040101F mov ecx,dword ptr [__imp_std::cout (402044h)]
00401025 push eax
00401026 push edx
00401027 push ecx
00401028 call std::operator<<<std::char_traits<char> > (4011D0h)
0040102D add esp,8
00401030 mov ecx,eax
00401032 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402040h)]
........
}
通过寄存器查看,得知strSrc未递增时的内存地址为:0x00402134,我们来看看这个地址的内存情况:
0x00402134 31 32 33 00 48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 123.H................
0x00402149 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .....................
0x0040215E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .....................
0x00402173 00 00 30 40 00 10 22 40 00 03 00 00 00 52 53 44 53 34 67 91 b9 ..0@.."@.....RSDS4g..
很明显,由于一开始的编译器优化,常量字符串“adf”并未定义在静态存储区中,也就是说不会出现在字符串“123”的后面了,所以strSrc经过三次递增后的输出是随机的,不可知的。
以上画线部分是对Debug版本和Release版本的分析。
总结:
这个问题出现的本质原因在于编译器对堆、栈和静态存储区的使用策略,以及VS开发环境对Debug版本和Release版本的处理方式不同。
Debug版本中,整个程序需要使用的常量字符串都被依次定义在静态存储区中了,于是就出现了这个奇怪的现象。
Release版本运行结果不一样,是因为这个版本并未将所有的常量字符串都依次定义在静态存储区中,如本例中看到的,从CPU寄存器直接拷贝到栈中,而不是先在静态存储区中定义这个字符串,再拷贝到栈中。