工作当中遇到过好几次比较诡异的问题,最后基本都是编译器捣的鬼,在此总结一下,以供大家参考,不对之处希望踊跃拍砖(peakflys原创作品,转载注明 )
编译环境:GCC 3.4.5 20051201 (项目组早期代码从04年开始的),为了脱离实际项目代码,下面仅用测试例子来反映基本一样的问题。
例一:
/**
*\author peakflys
*\brief 演示编译器“潜规则”
*/
#include <iostream>
#include <limits>
using namespace std;
typedef unsigned
int DWORD;
typedef unsigned
long QWORD;
int main()
{
cout<<"max unsigned int : \t"<<numeric_limits<DWORD>::max()<<endl;
cout<<"max unsigned long: \t"<<numeric_limits<QWORD>::max()<<endl;
DWORD st1 = 4200000000,st2 = 100000000;
QWORD i1 = st1 + st2; //或者直接就用4200000000 + 100000000
cout<<i1<<endl;
return 0;
}
我服务器上的运行结果:
max unsigned int : 4294967295
max unsigned long: 18446744073709551615
5032704
如果在高级别的警告环境下,编译会有溢出警告,而运行结果也证明了确实溢出了。
查看汇编如下:
0x0000000000400949 <main+101>: movl $0xfa56ea00,-0x18(%rbp)
0x0000000000400950 <main+108>: movl $0x5f5e100,-0x14(%rbp)
0x0000000000400957 <main+115>: mov -0x14(%rbp),%eax
0x000000000040095a <main+118>: add -0x18(%rbp),%eax
0x000000000040095d <main+121>: mov %eax,%eax
0x000000000040095f <main+123>: mov %rax,-0x10(%rbp)
0x0000000000400963 <main+127>: mov -0x10(%rbp),%rsi
0x0000000000400967 <main+131>: mov $0x600ed0,%edi
0x000000000040096c <main+136>: callq 0x400748 <_ZNSolsEm@plt>
原来编译器是先把st2值放在eax里,然后和st1相加,结果还是放在eax里,而eax是32位寄存器,自然溢出了……
修改代码及运行结果如下:
/**
*\author peakflys
*\brief 演示编译器“潜规则”
*/
#include <iostream>
#include <limits>
using namespace std;
typedef unsigned
int DWORD;
typedef unsigned
long QWORD;
int main()
{
cout<<"max unsigned int : \t"<<numeric_limits<DWORD>::max()<<endl;
cout<<"max unsigned long: \t"<<numeric_limits<QWORD>::max()<<endl;
DWORD st1 = 4200000000,st2 = 100000000;
QWORD i1 = (QWORD)st1 + st2;
cout<<i1<<endl;
return 0;
}
max unsigned int : 4294967295
max unsigned long: 18446744073709551615
4300000000
这次运行正确,直接disassemble,相加代码汇编如下:
0x0000000000400949 <main+101>: movl $0xfa56ea00,-0x18(%rbp)
0x0000000000400950 <main+108>: movl $0x5f5e100,-0x14(%rbp)
0x0000000000400957 <main+115>: mov -0x18(%rbp),%edx
0x000000000040095a <main+118>: mov -0x14(%rbp),%eax
0x000000000040095d <main+121>: lea (%rdx,%rax,1),%rax
0x0000000000400961 <main+125>: mov %rax,-0x10(%rbp)
0x0000000000400965 <main+129>: mov -0x10(%rbp),%rsi
0x0000000000400969 <main+133>: mov $0x600ed0,%edi
0x000000000040096e <main+138>: callq 0x400748 <_ZNSolsEm@plt>
可见这次编译器动用了两个寄存器edx和eax来做相加操作,结果在64为的rax里,自然不会溢出了。这个例子的原型是程序里处理玩家获得经验的经验公式,本来很多经验,最后只获得了极少的经验。
例二:
/**
*\author peakflys
*\brief 演示编译器自动优化
*/
#include <iostream>
using namespace std;
class A
{
public:
A(
const int _a) : a(a){}
int a;
};
int main()
{
cout<<"test1(const Class):\t";
const A ca(100);
A *pca = (A*)&ca;
pca->a = 50;
cout<<"initValue: 100"<<"\tconstValue:"<<ca.a<<"\tnonconstValue:"<<pca->a<<endl;
cout<<"test2(const int):\t";
const int a = 100;
int *pi = (
int *)&a;
*pi = 50;
cout<<"initValue: 100"<<"\tconstValue:"<<a<<"\tnonconstValue:"<<*pi<<endl;
cout<<"test3(const string):\t";
const string s("中国"); "
char *ps = const_cast<
char*> (s.c_str());
strcpy(ps,"美国"); "
cout<<"initValue: 中国"<<"\tconstValue:"<<s<<"\tnonconstValue:"<<ps<<endl;nd
return 0;
}
运行结果如下:
test1(const Class): initValue: 100 constValue:50 nonconstValue:50
test2(const int): initValue: 100 constValue:100 nonconstValue:50
test3(const string): initValue: 中国 constValue:美国 nonconstValue:美国
程序中强制去除const的代码很粗暴,很ugly,但是 实际使用中有时候不得不因为各种原因而使用这样的代码。上面例子中对于class类型(自定义的classA和系统的class string)和系统基本类型(上面的int)强制去掉栈对象的const属性再做操作是可行的(这样的const仅仅是编译器操作的const,如果是字符串常量等实际常量区的对象强行操作,操作系统会发飙的……),但是操作结果却不一样,查看汇编:
ump of assembler code for function main:
0x0000000000400b24 <main+0>: push %rbp
0x0000000000400b25 <main+1>: mov %rsp,%rbp
0x0000000000400b28 <main+4>: push %r12
0x0000000000400b2a <main+6>: push %rbx
0x0000000000400b2b <main+7>: sub $0x50,%rsp
***********************************test1*********************************************
0x0000000000400b2f <main+11>: mov $0x400e48,%esi
0x0000000000400b34 <main+16>: mov $0x601300,%edi
0x0000000000400b39 <main+21>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400b3e <main+26>: lea -0x30(%rbp),%rdi //ca的栈地址!
0x0000000000400b42 <main+30>: mov $0x64,%esi
0x0000000000400b47 <main+35>: callq 0x400d36 <A> //调用A构造函数
0x0000000000400b4c <main+40>: lea -0x30(%rbp),%rax
0x0000000000400b50 <main+44>: mov %rax,-0x28(%rbp) //pca的栈地址!
0x0000000000400b54 <main+48>: mov -0x28(%rbp),%rax
0x0000000000400b58 <main+52>: movl $0x32,(%rax) //pca所地址(即ca),赋值0x32(50)
0x0000000000400b5e <main+58>: mov -0x28(%rbp),%rax //从右向左压栈 ,先取出pca值
0x0000000000400b62 <main+62>: mov (%rax),%r12d //取出pca所指内存值
0x0000000000400b65 <main+65>: mov -0x30(%rbp),%ebx //取出ca值
0x0000000000400b68 <main+68>: mov $0x400e5d,%esi //
0x0000000000400b6d <main+73>: mov $0x601300,%edi //压栈esi、edi,装备调用callq
0x0000000000400b72 <main+78>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400b77 <main+83>: mov %rax,%rdi
0x0000000000400b7a <main+86>: mov $0x400e6c,%esi
0x0000000000400b7f <main+91>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400b84 <main+96>: mov %rax,%rdi
0x0000000000400b87 <main+99>: mov %ebx,%esi
0x0000000000400b89 <main+101>: callq 0x4008e0 <_ZNSolsEi@plt>
0x0000000000400b8e <main+106>: mov %rax,%rdi
0x0000000000400b91 <main+109>: mov $0x400e79,%esi
0x0000000000400b96 <main+114>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400b9b <main+119>: mov %rax,%rdi
0x0000000000400b9e <main+122>: mov %r12d,%esi
0x0000000000400ba1 <main+125>: callq 0x4008e0 <_ZNSolsEi@plt>
0x0000000000400ba6 <main+130>: mov %rax,%rdi
0x0000000000400ba9 <main+133>: mov $0x4009a0,%esi
0x0000000000400bae <main+138>: callq 0x400990 <_ZNSolsEPFRSoS_E@plt>
***********************************test2*********************************************
0x0000000000400bb3 <main+143>: mov $0x400e89,%esi
0x0000000000400bb8 <main+148>: mov $0x601300,%edi
0x0000000000400bbd <main+153>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400bc2 <main+158>: movl $0x64,-0x34(%rbp) //a地址,ox64(100)放入此处
0x0000000000400bc9 <main+165>: lea -0x34(%rbp),%rax
0x0000000000400bcd <main+169>: mov %rax,-0x20(%rbp) //pi地址,放入a地址
0x0000000000400bd1 <main+173>: mov -0x20(%rbp),%rax //取出pi地址
0x0000000000400bd5 <main+177>: movl $0x32,(%rax) //pi所指内容(即a)赋值50
0x0000000000400bdb <main+183>: mov -0x20(%rbp),%rax //从右向左压栈 ,先取出pi值
0x0000000000400bdf <main+187>: mov (%rax),%ebx //pi所指内存值
//此处没有取a的值???
0x0000000000400be1 <main+189>: mov $0x400e5d,%esi //
0x0000000000400be6 <main+194>: mov $0x601300,%edi //压栈esi、edi,装备调用callq
0x0000000000400beb <main+199>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400bf0 <main+204>: mov %rax,%rdi
0x0000000000400bf3 <main+207>: mov $0x400e6c,%esi
0x0000000000400bf8 <main+212>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400bfd <main+217>: mov %rax,%rdi
0x0000000000400c00 <main+220>: mov $0x64,%esi
0x0000000000400c05 <main+225>: callq 0x4008e0 <_ZNSolsEi@plt>
0x0000000000400c0a <main+230>: mov %rax,%rdi
0x0000000000400c0d <main+233>: mov $0x400e79,%esi
0x0000000000400c12 <main+238>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400c17 <main+243>: mov %rax,%rdi
0x0000000000400c1a <main+246>: mov %ebx,%esi
0x0000000000400c1c <main+248>: callq 0x4008e0 <_ZNSolsEi@plt>
0x0000000000400c21 <main+253>: mov %rax,%rdi
0x0000000000400c24 <main+256>: mov $0x4009a0,%esi
0x0000000000400c29 <main+261>: callq 0x400990 <_ZNSolsEPFRSoS_E@plt>
***********************************test3*********************************************
0x0000000000400c2e <main+266>: mov $0x400e9c,%esi
0x0000000000400c33 <main+271>: mov $0x601300,%edi
0x0000000000400c38 <main+276>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400c3d <main+281>: lea -0x29(%rbp),%rdi
0x0000000000400c41 <main+285>: callq 0x4009b0 <_ZNSaIcEC1Ev@plt>
0x0000000000400c46 <main+290>: lea -0x29(%rbp),%rdx
0x0000000000400c4a <main+294>: lea -0x40(%rbp),%rdi
0x0000000000400c4e <main+298>: mov $0x400eb2,%esi
0x0000000000400c53 <main+303>: callq 0x400960 <_ZNSsC1EPKcRKSaIcE@plt>
0x0000000000400c58 <main+308>: lea -0x29(%rbp),%rdi
0x0000000000400c5c <main+312>: callq 0x400980 <_ZNSaIcED1Ev@plt>
0x0000000000400c61 <main+317>: lea -0x40(%rbp),%rdi
0x0000000000400c65 <main+321>: callq 0x4008f0 <_ZNKSs5c_strEv@plt>
0x0000000000400c6a <main+326>: mov %rax,-0x48(%rbp)
0x0000000000400c6e <main+330>: jmp 0x400c8e <main+362>
0x0000000000400c70 <main+332>: mov %rax,-0x58(%rbp)
0x0000000000400c74 <main+336>: mov -0x58(%rbp),%rbx
0x0000000000400c78 <main+340>: lea -0x29(%rbp),%rdi
0x0000000000400c7c <main+344>: callq 0x400980 <_ZNSaIcED1Ev@plt>
0x0000000000400c81 <main+349>: mov %rbx,-0x58(%rbp)
0x0000000000400c85 <main+353>: mov -0x58(%rbp),%rdi
0x0000000000400c89 <main+357>: callq 0x4009d0 <_Unwind_Resume@plt>
0x0000000000400c8e <main+362>: mov -0x48(%rbp),%rax
0x0000000000400c92 <main+366>: mov %rax,-0x18(%rbp)
0x0000000000400c96 <main+370>: mov -0x18(%rbp),%rax
0x0000000000400c9a <main+374>: movl $0xe58ebee7,(%rax)
0x0000000000400ca0 <main+380>: movw $0xbd9b,0x4(%rax)
0x0000000000400ca6 <main+386>: movb $0x0,0x6(%rax)
0x0000000000400caa <main+390>: mov $0x400eb9,%esi
0x0000000000400caf <main+395>: mov $0x601300,%edi
0x0000000000400cb4 <main+400>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400cb9 <main+405>: mov %rax,%rdi
0x0000000000400cbc <main+408>: mov $0x400e6c,%esi
0x0000000000400cc1 <main+413>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400cc6 <main+418>: mov %rax,%rdi
0x0000000000400cc9 <main+421>: lea -0x40(%rbp),%rsi
0x0000000000400ccd <main+425>: callq 0x400970 <_ZStlsIcSt11char_traitsIcESaIcEERSt13basic_ostreamIT_T0_ES7_RKSbIS4_S5_T1_E@plt>
0x0000000000400cd2 <main+430>: mov %rax,%rdi
0x0000000000400cd5 <main+433>: mov $0x400e79,%esi
0x0000000000400cda <main+438>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400cdf <main+443>: mov %rax,%rdi
0x0000000000400ce2 <main+446>: mov -0x18(%rbp),%rsi
0x0000000000400ce6 <main+450>: callq 0x400940 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x0000000000400ceb <main+455>: mov %rax,%rdi
0x0000000000400cee <main+458>: mov $0x4009a0,%esi
0x0000000000400cf3 <main+463>: callq 0x400990 <_ZNSolsEPFRSoS_E@plt>
0x0000000000400cf8 <main+468>: mov $0x0,%ebx
0x0000000000400cfd <main+473>: lea -0x40(%rbp),%rdi
0x0000000000400d01 <main+477>: callq 0x400950 <_ZNSsD1Ev@plt>
0x0000000000400d06 <main+482>: mov %ebx,-0x4c(%rbp)
0x0000000000400d09 <main+485>: jmp 0x400d29 <main+517>
0x0000000000400d0b <main+487>: mov %rax,-0x58(%rbp)
0x0000000000400d0f <main+491>: mov -0x58(%rbp),%rbx
0x0000000000400d13 <main+495>: lea -0x40(%rbp),%rdi
0x0000000000400d17 <main+499>: callq 0x400950 <_ZNSsD1Ev@plt>
0x0000000000400d1c <main+504>: mov %rbx,-0x58(%rbp)
0x0000000000400d20 <main+508>: mov -0x58(%rbp),%rdi
0x0000000000400d24 <main+512>: callq 0x4009d0 <_Unwind_Resume@plt>
0x0000000000400d29 <main+517>: mov -0x4c(%rbp),%eax
0x0000000000400d2c <main+520>: add $0x50,%rsp
0x0000000000400d30 <main+524>: pop %rbx
0x0000000000400d31 <main+525>: pop %r12
0x0000000000400d33 <main+527>: leaveq
0x0000000000400d34 <main+528>: retq
因test1和test3基本原理一样,故上面汇编注释只有test1和test2的,从上面汇编可以看出,编译器调用cout时,参数入栈是从右向左的,对于test1是取出pca所指内存值入栈,再取ca所指内存值入栈,然后调用cout输出。但是test2入栈的却只有pi所指内存值,i并没有入栈。汇编没显示,个人认为 应该是编译器在汇编时就做了优化,把cout中的常量a直接替换掉了。这种错误在实际项目中显现出的问题很诡异,刚开始也很难查,所以强转基本类型const时注意这种情况。
例三、待续……
(peakflys原创作品,转载注明 )