凡本论坛原创内容,其作者享有著作权,未经许可谢绝转载。
Lightning[0GiNr]
这些日子在写一些代码时遇到了一件令人非常囧的事情:同样的源码,DEBUG版本行为完全正常,RELEASE版本行为完全不正常。
按以往的经验,这一般都是#ifdef _DEBUG之类的宏导致了两个版本的行为不一致,但是我的程序中并没有显式或者隐式地使用过这个宏。由于Release版本默认不支持调试,于是就把Release版本里挂上调试信息来试试。
在Project->Sittings的c/c++选项卡optimizations中选上Disable[DEBUG],Debug Info里填上Program Database,Link选项卡中搞定Generate debug info和Generate mapfile。
然后一按F5,行为完全和DEBUG版本一样正常!
可是一换回默认的release,问题就会出现,狂郁闷……
注释了几处代码,并且用printf输出了一些LOG后,我渐渐发现了问题所在:竟然是字符串操作的问题!
为了便于说明,我先写一个例子:- #include <windows.h>
- #include <stdio.h>
- int InsertString(char* pStr)
- {
- strcpy(pStr, "world");
- return strlen(pStr);
- }
- int CreateString(char* pStr)
- {
- strcpy(pStr, "Hello ");
- return strlen(pStr) + InsertString(pStr + strlen(pStr));
- }
- int main(void)
- {
- char szHello[64] = "";
- int nLen = CreateString(szHello);
- printf("%s LEN = %d\n", szHello, nLen);
- system("pause");
- return 0;
- }
复制代码 你能猜出程序的输出是什么吗?
DEBUG:
RELEASE:
数一下字符也知道,Release版本给出的LEN = 16是绝对错误的,问题出在CreateString函数的这一行:- return strlen(pStr) + InsertString(pStr + strlen(pStr));
复制代码 在DEBUG版本或者加了调试信息的Release版中,VC编译器的编译结果是先计算加号左边的表达式,再计算右边的,这样,第一个strlen(pStr)计算的仅仅是"Hello "这个字符串的长度。相应的汇编代码:- 004010B9 8B 4D 08 mov ecx,dword ptr [ebp+8]
- 004010BC 51 push ecx
- 004010BD E8 DE 00 00 00 call strlen (004011a0) // 从左到右计算
- 004010C2 83 C4 04 add esp,4
- 004010C5 8B F0 mov esi,eax
- 004010C7 8B 55 08 mov edx,dword ptr [ebp+8]
- 004010CA 52 push edx
- 004010CB E8 D0 00 00 00 call strlen (004011a0)
- 004010D0 83 C4 04 add esp,4
- 004010D3 8B 4D 08 mov ecx,dword ptr [ebp+8]
- 004010D6 03 C8 add ecx,eax
- 004010D8 51 push ecx
- 004010D9 E8 2C FF FF FF call @ILT+5(InsertString) (0040100a)
- 004010DE 83 C4 04 add esp,4
- 004010E1 03 C6 add
- 但是在Release版本中就不一样了:
- 0040106C 8B FB mov edi,ebx
- 0040106E 83 C9 FF or ecx,0FFh
- 00401071 33 C0 xor eax,eax
- 00401073 F2 AE repne scas byte ptr [edi] // 这里是后面那个strlen
- 00401075 F7 D1 not ecx
- 00401077 49 dec ecx
- 00401078 03 CB add ecx,ebx // pStr + strlen(pStr);
- 0040107A 51 push ecx
- 0040107B E8 80 FF FF FF call 00401000 // 这里先调用了InsertString 函数
- 00401080 8B D0 mov edx,eax
- 00401082 8B FB mov edi,ebx
- 00401084 83 C9 FF or ecx,0FFh
- 00401087 33 C0 xor eax,eax
- 00401089 83 C4 04 add esp,4
- 0040108C F2 AE repne scas byte ptr [edi] // 然后把InsertString修改过的字符串进行strlen
- 0040108E F7 D1 not ecx
- 00401090 49 dec ecx
- 00401091 5F pop edi
- 00401092 03 D1 add edx,ecx
- 00401094 5E pop esi
- 00401095 8B C2 mov eax,edx
- 00401097 5B pop ebx
- 00401098 5D pop ebp
- 00401099 C3 ret
复制代码 这样,最终的结果就是strlen("Hello world") + strlen("world") = 16了。
通过这个实例可以看出,类似于return strlen(pStr) + InsertString(pStr + strlen(pStr));之类的语句是应该在编程过程中尽力避免的,虽然一些C++手册上关于运算符结合顺序的论述会明确告诉你它会先计算加号前面的表达式(+的结合性是从左到右),但是并不是每一个非标准的编译器在每一个编译选项时都会这么做(有时出于优化的目的会调整顺序)。而且这样做让别人看你的代码时也很容易迷惑不解。
解决方法:- int CreateString(char* pStr)
- {
- strcpy(pStr, "Hello ");
- int nLen = strlen(pStr);
- return nLen + InsertString(pStr + nLen );
- }
复制代码 |
附件:
您需要登录才可以下载或查看附件。没有帐号?注册