返回列表 发帖

[C/Java] [VC笔记]Debug和Release的困惑

凡本论坛原创内容,其作者享有著作权,未经许可谢绝转载。

[C/Java] [VC笔记]Debug和Release的困惑

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后,我渐渐发现了问题所在:竟然是字符串操作的问题!
为了便于说明,我先写一个例子:
  1. #include <windows.h>
  2. #include <stdio.h>

  3. int InsertString(char* pStr)
  4. {
  5.     strcpy(pStr, "world");
  6.     return strlen(pStr);
  7. }

  8. int CreateString(char* pStr)
  9. {
  10.     strcpy(pStr, "Hello ");
  11.     return strlen(pStr) + InsertString(pStr + strlen(pStr));
  12. }

  13. int main(void)
  14. {
  15.     char szHello[64] = "";
  16.     int nLen = CreateString(szHello);
  17.     printf("%s LEN = %d\n", szHello, nLen);
  18.     system("pause");
  19.     return 0;
  20. }
复制代码
你能猜出程序的输出是什么吗?

DEBUG:

RELEASE:

数一下字符也知道,Release版本给出的LEN = 16是绝对错误的,问题出在CreateString函数的这一行:
  1. return strlen(pStr) + InsertString(pStr + strlen(pStr));
复制代码
在DEBUG版本或者加了调试信息的Release版中,VC编译器的编译结果是先计算加号左边的表达式,再计算右边的,这样,第一个strlen(pStr)计算的仅仅是"Hello "这个字符串的长度。相应的汇编代码:
  1. 004010B9 8B 4D 08             mov         ecx,dword ptr [ebp+8]
  2. 004010BC 51                   push        ecx
  3. 004010BD E8 DE 00 00 00       call        strlen (004011a0) // 从左到右计算
  4. 004010C2 83 C4 04             add         esp,4
  5. 004010C5 8B F0                mov         esi,eax
  6. 004010C7 8B 55 08             mov         edx,dword ptr [ebp+8]
  7. 004010CA 52                   push        edx
  8. 004010CB E8 D0 00 00 00       call        strlen (004011a0)
  9. 004010D0 83 C4 04             add         esp,4
  10. 004010D3 8B 4D 08             mov         ecx,dword ptr [ebp+8]
  11. 004010D6 03 C8                add         ecx,eax
  12. 004010D8 51                   push        ecx
  13. 004010D9 E8 2C FF FF FF       call        @ILT+5(InsertString) (0040100a)
  14. 004010DE 83 C4 04             add         esp,4
  15. 004010E1 03 C6                add      

  16. 但是在Release版本中就不一样了:

  17. 0040106C 8B FB                mov         edi,ebx
  18. 0040106E 83 C9 FF             or          ecx,0FFh
  19. 00401071 33 C0                xor         eax,eax
  20. 00401073 F2 AE                repne scas byte ptr [edi] // 这里是后面那个strlen
  21. 00401075 F7 D1                not         ecx
  22. 00401077 49                   dec         ecx
  23. 00401078 03 CB                add         ecx,ebx // pStr + strlen(pStr);
  24. 0040107A 51                   push        ecx
  25. 0040107B E8 80 FF FF FF       call        00401000 // 这里先调用了InsertString 函数
  26. 00401080 8B D0                mov         edx,eax
  27. 00401082 8B FB                mov         edi,ebx
  28. 00401084 83 C9 FF             or          ecx,0FFh
  29. 00401087 33 C0                xor         eax,eax
  30. 00401089 83 C4 04             add         esp,4
  31. 0040108C F2 AE                repne scas byte ptr [edi] // 然后把InsertString修改过的字符串进行strlen
  32. 0040108E F7 D1                not         ecx
  33. 00401090 49                   dec         ecx
  34. 00401091 5F                   pop         edi
  35. 00401092 03 D1                add         edx,ecx
  36. 00401094 5E                   pop         esi
  37. 00401095 8B C2                mov         eax,edx
  38. 00401097 5B                   pop         ebx
  39. 00401098 5D                   pop         ebp
  40. 00401099 C3                   ret
复制代码
这样,最终的结果就是strlen("Hello world") + strlen("world") = 16了。

通过这个实例可以看出,类似于return strlen(pStr) + InsertString(pStr + strlen(pStr));之类的语句是应该在编程过程中尽力避免的,虽然一些C++手册上关于运算符结合顺序的论述会明确告诉你它会先计算加号前面的表达式(+的结合性是从左到右),但是并不是每一个非标准的编译器在每一个编译选项时都会这么做(有时出于优化的目的会调整顺序)。而且这样做让别人看你的代码时也很容易迷惑不解。

解决方法:
  1. int CreateString(char* pStr)
  2. {
  3.     strcpy(pStr, "Hello ");
  4.     int nLen = strlen(pStr);
  5.     return nLen + InsertString(pStr + nLen );
  6. }
复制代码
附件: 您需要登录才可以下载或查看附件。没有帐号?注册

这个强悍~~学习~

TOP

返回列表