今天帮同事解决了一个很奇怪的问题,一个定义返回值为unsigned long long的函数在实际调用中竟然永远返回0,就算你在此函数内返回任意整数调用者获得的都是0.
硬件平台为PPC8313,操作系统为嵌入式Linux,编译器为g++的交叉编译器,版本为4.2.2.
环境比较复杂,但问题简化后总共涉及三个文件,liba.h定义一些函数接口和liba.cpp实现liba.h定义的函数接口,这两个文件在编译后生成一个名字为liba.a的静态库,main.cpp调用liba.a内的接口,最后和liba.a一起链接生成名为test的可执行文件。
三个文件代码简化如下:
liba.h内容如下:
/* liba.h */
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif /* end of defined __cplusplus */
unsigned long long GetSize();
#ifdef __cplusplus
}
#endif /* end of defined __cplusplus */
/* end of liba.h */
liba.cpp内容如下:
/* liba.cpp */
#include
#include "liba.h"
using namespace std;
unsigned long long GetSize()
{
unsigned long long ret = 123456;
printf("In function GetSize() ret = %llu
",ret);
return ret;
}
/* end of liba.cpp */
main.c内容如下:
/* main.c */
#include
//#include "liba.h" /* 这里没有引用liba.h,问题就出在这 */
int main(int argc, char** argv)
{
unsigned long long temp = GetSize();
printf("In main() GetSize() = %llu
",temp);
return 0;
}
/* end of main.c */
我一般喜欢bjam来编写编译规则,jamroot.jam内容如下:
# jamroot.jam
lib a : liba.cpp ;
exe test : main.c a ;
第一行“#”表示注释,第二行表示依赖liba.cpp编译生成liba库,第三行表示依赖main.c和liba生成名字为test的可执行文件。(bjam可以简单地构建简单的目标,可管理地构建复杂的目标,如此可见一斑,确实够强悍的。)
设置好bjam交叉编译环境,在Linux下执行:bjam toolset=gcc-ppc8313 link=static即可生成相应的库和可执行文件。
传到8313板上,chmod后执行,竟然输出:
In function GetSize() ret = 123456
In main() GetSize() = 0
不管你如何修改ret的值,也不管你是debug版本和release版本(甚至换其它编译链接参数),在main函数内获得GetSize()的返回值永远为0。
几个同事搞了好几天,一直都不知道是什么原因,奇怪的是同样一份代码,使用X86的g++没有任何问题,在PPC880和PPC8548、ARM Cortex A8等硬件平台下均没有任何问题,但在PPC8313下却有问题。此问题之玄乎竟成了我们部门的疑难杂症。后来同事求助于我,刚好我手上的东西不是很急,也刚好想研究一下这种疑难杂症(毕竟有问题才能学到真东西),所以我叫他发代码给我研究一下。经过一番试验后,同事的描述都是对的,在X86下没有任何问题,但在某块PPC8313开发板上竟然有这样的问题,也暂时把我难住了好一会,首先怀疑交叉编译器问题,但同样的一个可执行文件在同为PPC8313的板上竟然有些执行正确有些执行不正确;再就怀疑这块板内核和文件系统不同,经过跟同为PPC8313的正常的板信息对比后,都是一样的,取消了这个怀疑;后来我又怀疑是不是这块板对unsigned
long long类型支持有问题,经过写测试程序和改变类型,就算我把代码里面的unsigned long long改为int类型,有问题的板同样返回0;我开始从编译过程来查找问题了,因为同事的实际代码比较多,编译GetSize()函数时有个很不明显的警告:
warning: implicit declaration of function ‘GetSize’
以我多年“种田”的经验,在这里肯定是有问题的,出现这种警告一般就两个原因:
1:没有把函数所在的源文件生成.o目标文件;
2:在函数所在的源文件中调用了,但是引用相关的头文件进行声明。
同事的代码一看就是第二种原因,给代码增加相应头文件进行声明后编译警告消除,然后交叉编译放板上,奇迹发生了,竟然输出了正确的结果。
用相应的objdump工具查看汇编代码,同个版本(debug/release)GetSize()函数生成生成的汇编代码完全一模一样,但在函数调用返回后的处理有点不同,这个不同是造成这个问题的根本原因。
得到教训:
1:头文件不是可有可无的。gcc的检查比较弱(虽然文件名为cpp但文件中使用extern ”C“),不引用头文件但有链接实现的库照样可以链接成功。如果不引用头文件,编译链接可能没问题,但在使用中可能会遇到很奇怪的问题,就如我们遇到的这个疑难杂症一样,编写代码时一定要注意。
2:编译代码不能忽视任何的警告信息,最好保持代码编译的零警告,窗户有个洞如果一直都不堵的话这个洞会越来越大的。
3:错误发现的越早越好,能在编译时发现的错误,不要拖到运行时;能在编辑时发现的错误,不要拖到编译时。
4:要有正确的编码规范。
5:最好在开发中使用一些静态代码检查工具辅助开发,如cppcheck和pc-link等都是不错的静态代码检查工具。