最近项目代码需要从mips平台移植到x86平台,这是公司产品第一次采用x86平台。之前项目很紧,所以很多代码都没有考虑移植性问题,因此移植的时候遇到了不少问题。前几天才解决了位序(也叫比特序,与字节序不同)问题,今天又遇到了一个比较隐蔽的C语言问题,在这里记录一下,告诫自己,也告诫各位同行,避免犯这样的错误。至于位序问题,以后应该会再另写一篇文章来说明。
原本在mips平台上运行良好的代码,移植到了x86平台,结果却不对了,我们仔细分析了代码,没发现什么可疑的地方,而且我之前为了优化那段代码,单独把那段代码抽出来测试过。我抽出来的代码在两个平台里得出的都是一样的结果。我对比了代码,实现的地方没有任何改动,照理说不应该出现这种情况的。不过根据打印出来的值,我注意到了一种情况,在x86平台里的结果值只有16位,但在mips平台里的结果值有32位,并且低16位的值与x86平台下的值一样。最后,我查看了声明该函数的头文件,才发现头文件里函数的声明与C文件里的实现返回值不一致!
问题可以简化成下面的代码:
- //crc.c
- //注意,此处没有包含crc.h这个头文件!
- unsigned int get_crc(void)
- {
- return 0x12345678;
- }
- //crc.h
- unsigned short get_crc(void);
- //main.c
- #include <stdio.h>
- #include "crc.h"
- int main(int argc, char *argv[])
- {
- unsigned int crc = get_crc();
- printf("crc:%x\n", crc);
- return 0;
- }
编译执行: gcc -Wall -o test main.c crc.c //好吧,-Wall也没办法报错
在x86平台下输出:5678
我又分别在mips平台和powerpc平台下编译执行了这段代码,同样没警告或者报错。在mips平台下输出:12345678,在powerpc平台下输出:12345678
在简化的代码里,大家很容易就能看出是get_crc这个函数的声明和定义(实现)不一致导致的问题,但在庞大的项目文件里,可能就没那么容易看出问题所在了。
我们在写代码的时候,往往只注意函数的实现,对函数的声明重视不足。在Linux平台下,我们喜欢用cscope+ctags+vim来写代码,修改或者浏览代码的时候也喜欢跳到函数定义处,变量声明处,却很少关注函数声明,导致修改代码之后声明和定义不一致的情形。
这并不只是程序新手才会出现的问题,工作几年的程序员也可能会犯这样的错误,出现问题的这段代码,就是出自一个已经工作了四年的同事之手。
也正是在这个时候,我才发现,我们之前的代码是有问题的,只是所谓的“得到了正确的结果”。
我起先认为对于这种情况,是个编译器未定义形为,不同gcc版本对这种情况的处理可能不一样,但我进行了一些测试,发现情况比我想象中的复杂。在powerpc平台,gcc版本是3.3.x,mips平台,gcc版本是4.3.x,在x86平台,有两个版本的编译器,分别为4.1.x(centos),4,6.x(ubuntu)执行情况是mips平台和powerpc平台一样,都是12345678,x86平台下均为5678。mips和powerpc都是大端,x86是小端,至令我没办法判断真正的问题在哪,是编译器版本原因还是与大小端有那么点关系。还望知道的朋友不吝赐教。
此外,我还测试了对于变量的情况,发现对于变量的处理,各个gcc版本不同平台都是一致的,当然,由于大小端的关系,输出结果会不同。大家有兴趣可以试一下。
说了那么多,只是想说明这个隐蔽的错误大家一不小心就很容易犯,而且后果也比较严重,得找到方法避免。
解决办法很简单,那就是通过把函数声明(原型)放在头文件中,而函数定义则放在另一个包含了该头文件的源文件中。这样编译器就能发现不一致的情况从而报错提醒我们。这个问题在《C专家编程》8.5节有论述。
我单独提出来的代码之所以结果一致,是因为我把函数定义跟对该函数的引用都放一个文件中了,没有使用头文件。
4月18日补充:由于之前mips平台和x86平台gcc版本不一样,没有可比性,今天到公司换了一样的gcc版本进行测试,发现mips平台下还是输出12345678,这看来应该是编译器后端的行为,有时间看下mips汇编确认一下吧。
阅读(3793) | 评论(0) | 转发(0) |