Chinaunix首页 | 论坛 | 博客
  • 博客访问: 144141
  • 博文数量: 23
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 326
  • 用 户 组: 普通用户
  • 注册时间: 2014-02-26 10:49
个人简介

记忆总是会慢慢褪去,所以让文字记住一切~

文章分类

全部博文(23)

文章存档

2017年(5)

2016年(3)

2015年(9)

2014年(6)

我的朋友

分类: C/C++

2014-10-27 14:13:54

      前一段是时间一直比较忙,终于项目快GM了。来喘口气,分享之前遇到的一个问题。在公司开发产品,写完代码一般会进行Code Review。由于和我搭档的同事比较忙,所以review就没有去做(也许是对我写的代码足够放心~)。偶尔闲了下来,无聊翻看之前写的代码,忽然眼前一亮,一个函数的实现在某一条执行分支上会没有返回值。实际函数的原型应该具有返回值的。大体可简化如下的一个示例。

点击(此处)折叠或打开

  1. #include <iostream>

  2. using namespace std;

  3. int get(int);

  4. int main(int argc, char* argv[])
  5. {
  6.     int value = get(argc);
  7.     cout<<"Value: "<<value<<endl;
  8.     return 0;
  9. }

  10. int get(int argc)
  11. {
  12.     if(argc > 1)
  13.         return 9;
  14. }
      可以看到,函数getargc <= 1时,没有return语句调用。对于比较好的编译器,也许会提醒程序员一下,如在vs2008中会提示:warning C4715: 'get' : not all control paths return a value


      如果忽略这样一个warning,或者编译器干脆没有提示,这样一段代码如果执行会发生什么呢?运行发现结果如下:

     

      可以发现,当argc =0 时,get函数返回了1,那么这个1是哪里来的呢?我们看一下get函数的汇编代码:

     


      可以看到第三行汇编指令sub $0x4, %esp将栈顶指针下移了4个字节,预留了4个字节的空间,其实际被用作临时保存返回值。可以看出:

      1)  在正确返回时,其会将$ebp-0x4存放的值复制给eax寄存器(0x0804869c地址处指令),可以发现,最终eax存放的将是get函数返回给调用者的数据;

      2)  当不能正确执行返回语句:从get(int)+22 -> get(int)+27 -> leave -> ret,此间eax寄存器没有被任何指令赋值,所以eax寄存器的值将保留其进入函数的值。

      那么eax在进入函数之前它存放的值是哪里来的呢?

      

      可以看出eax的内容取自ecx寄存器存放地址所指向的值,如下图所示,即在进入get函数之前,eax将被赋值为1

     

      其实ecx存放的即为main函数调用时参数的地址,接下来就不深究了,这跟整个函数的调用过程中的栈变化有很大的关系。

 

      假设我们get函数没有参数传入,改为如下:

点击(此处)折叠或打开

  1. int get(int argc)
  2. {
  3.     if(0 > 1)
  4.         return 9;
  5. }

      那么运行结果会很不确定,因为eax寄存器的值此时也不是某一个传递参数的值      
     

 

      【结论】:当我们的函数在某一条分支上没有返回值时,eax 寄存器的值将作为返回值赋给函数调用者,而eax的值一般又与我们调用时提供的参数有关系,如果我们的函数没有提供参数,那么eax寄存器中的值就会很不确定,总之,如果漏掉了一个返回分支,隐含的bug会让程序在某一天突然崩溃的~

 

      p.s. 本以为自己犯了一个很严重的错误,最后发现我查看的代码并非最终版本,中间由于修改代码逻辑产生了这样的一个片断(被check in 到代码库中了),原来是虚惊一场~。~

阅读(3225) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~