//========================================================================
//TITLE:
// VS2005和EVC4字符串宏在MIPSII架构的比较
//AUTHOR:
// norains
//DATE:
// Friday 21-December-2007
//Environment:
// EVC4.0 + SDK-WINCE5.0-MIPSII
// VS2005 + SDK-WINCE5.0-MIPSII
//========================================================================
今天调试程序时,遇到一个摸不着头绪的问题.同样的代码,在EVC4.0编译之后能正常运行,而在VS2005中也能正常通过编译,但偏偏一运行,就提出内存越界错误.
提炼之后的简单代码如下:
TCHAR szBuf[MAX_PATH]
=
{
0
};
#define
STR_DEF TEXT("DEFINE App")
_tcscpy(szBuf,_tcslwr(STR_DEF));
现在我们来剖析一下.
在此之前来说明一下实验环境:
VS2005 英文版和EVC4.0,选择的都采用MIPSII进行DEBUG版本的编译.需要注意的是,因为编译器的版本实现问题,无法保证本文的结果能在其它编译器(如ARM,模拟器等)中重现.
先来一段简单的代码做范例:
int
WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int
nCmdShow)
{
#define
STR_DEF TEXT("DEFINE App")
STR_DEF[
0
]
=
'
G
'
;
return
0
;
}
这段代码在EVC4.0下顺利编译通过,但在vs2005就出现编译错误:error C2166: l-value specifies const object. 很明显,在vs2005中将STR_DEF作为const字符串,所以STR_DEF[0] = 'G'这语句肯定会出错.换句话说,EVC4.0版本中并没有将STR_DEF当成const变量.现在我们不去考虑vs2005,只在evc4.0范围中考虑:更改了字符之后,原宏定义会不会受到影响?在代码中增添一句显示如下:
int
WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int
nCmdShow)
{
#define
STR_DEF TEXT("DEFINE App")
STR_DEF[
0
]
=
'
G
'
;
MessageBox(NULL,STR_DEF,TEXT(
""
),MB_OK);
return
0
;
}
显示的对话框如下(图一):
我们可以看出,在MessageBox()中的STR_DEF宏并没有受之前赋值的影响.此STR_DEF非彼之STR_DEF.
回到文章开头的_tcslwr函数,我们将代码更改如下:
int
WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int
nCmdShow)
{
#define
STR_DEF TEXT("DEFINE App")
MessageBox(NULL,_tcslwr(STR_DEF),TEXT(
""
),MB_OK);
return
0
;
}
幸运的是,这段更改后的代码,无论是在vs2005还是在evc4.0中都能正确编译通过;不幸的是,编译出来的程序运行结果完全不同:
EVC4.0编译的程序运行截图(图二):
从图上我们可以看出,STR_DEF已经完美的转化为小写,并且很清晰明了的显示了出来.
那么vs2005编译出来的程序情况如何呢?截图如下(图三)
异常0xC0000005 ! 最熟悉不过的异常了,内存溢出!
先将这个异常放一放,我们先来看看_tcslwr函数的定义:
TCHAR *_tcslwr( TCHAR *string );
最关键的是返回值的描述:
Both of these functions return a pointer to the converted string. Because the modification is done in place, the pointer returned is the same as the pointer passed as the input argument. No return value is reserved to indicate an error.
这段英文的大意是:返回的是指向转换后的字符串的指针.因为这转换是在内部进行的,所以这转换后的指针和传入的指针地址是相同的.
回头再来看看个函数调用:_tcslwr(STR_DEF).按之前的说明,那么调用_tcslwr之后返回的应该是STR_DEF的指针!如果从函数来看,_tcslwr的形参不是const,那么vs2005应该无法编译通过才是,难道vs2005在编译时不符合C++规范?为了验证这点,我们先写一个空函数测试一下:
TCHAR
*
Test(TCHAR
*
pStr)
{
return
pStr;
}
int
WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int
nCmdShow)
{
#define
STR_DEF TEXT("DEFINE App")
MessageBox(NULL,_tcslwr(STR_DEF),TEXT(
""
),MB_OK);
Test(STR_DEF);
//
居然可以编译通过
return
0
;
}
我的天呐,VS2005究竟将字符串宏解释成什么了?因为如果有这种代码,vs2005中是肯定通不过的(当然evc4.0也不行):
const
TCHAR cszStr[
10
]
=
{
0
};
Test(cszStr);
//
无法编译通过
难道这时候vs2005又把字符串宏不当成const了?这不知道算不算vs2005的一个bug?
但这还是无法解释为何同样的代码在vs2005和evc4.0编译之后的运行结果会迥然不同.我们再把代码更改一下,看看会出现什么结果:
TCHAR
*
Test(TCHAR
*
pStr)
{
return
pStr;
}
int
WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int
nCmdShow)
{
#define
STR_DEF TEXT("DEFINE App")
TCHAR
*
pReturn
=
Test(STR_DEF);
pReturn[
0
]
=
'
A
'
;
return
0
;
}
这段代码VS2005和EVC4.0中都能顺利编译通过,但如果采用单步调试方式,我们将发现pReturn[0] = 'A'这语句在VS2005中出错!而与此相反,该语句却能在EVC4.0中畅行无阻.根据以上的情况,我们完全有理由可以大胆推测,造成本文开头所示的错误的一个最重要原因是:临时变量的生存周期不同.
还是以刚刚那段代码为例子.TCHAR *pReturn = Test(STR_DEF)这段代码构建了一个临时变量(在这里我们暂时称之为pStr),而pStr在vs2005的生存周期是在函数Test()体内,而在evc中却是在WinMain()函数!所以就不难解释为何通过返回指针赋值(即代码中的pReturn[0] = 'A'),在两种编译器中有完全不同的表现了!
这不由得让我们想起了一个经典的代码:
for(int i = 0; i < 10; i ++)
{}
由于对C++标准的支持不同,所以导致变量i的生存周期在不同的编译器中可能是不同的!(根据C++99标准,i生命周期应该只存在于循环体内)
虽然是如此猜测,但实际上是否如此呢?分别设置vs2005和evc,让其产生cod文件,通过查看cod的汇编代码来证明我们的推测:
VS2005编译Test函数的汇编代码:
?
Test@@YAPA_WPA_W@Z: # Test
.
set
noreorder
$LN5@Test:
00000
fff8 27bd addiu sp,sp,
0xFFF8
00004
.frame sp,
8
, RA #
0x00000008
00004
.mask
0x00000000
,
0x00000000
# (
0
)
00004
.fmask
0x00000000
,
0x00000000
# (
0
)
00004
.prologue
0
$M28923:
00004
0008
afa4 sw a0,pStr$(sp)
00008
0008
8fa2 lw v0,pStr$(sp)
0000c
0000
afa2 sw v0,$T28922(sp)
EVC4.0编译Test函数的汇编代码:
pStr$
=
8
$T27371
=
0
?
Test@@YAPAGPAG@Z: # Test
.
set
noreorder
00000
fff8 27bd addiu sp,sp,
0xFFF8
00004
.frame sp,
8
, RA #
0x00000008
00004
.mask
0x00000000
,
0x00000000
# (
0
)
00004
.fmask
0x00000000
,
0x00000000
# (
0
)
00004
.prologue
0
$M27372:
00004
0008
afa4 sw a0,pStr$(sp)
00008
0008
8fa2 lw v0,pStr$(sp)
0000c
0000
afa2 sw v0,$T27371(sp)
00010
0000
8fa2 lw v0,$T27371(sp)
看到了么?函数的汇编代码基本相同,但却有一点点小差异,而这小差异,正是导致vs2005和evc4.0编译的程序运行状态完全迥异的原因!
这一小差异,就是evc在函数体外定义了pStr$(指的是 pStr$ = 8 这语句段)! 也就是说,pStr$的生命周期是全局的,而这恰好印证了我们之前的推测.
根据C++99标准,vs2005的行为才是最符合的.在把项目从evc4.0迁移到vs2005过程中,如果出现之前尚未显现过的异常情况,不妨从标准入手,说不定会有意外的收获.