返回值截断-不要忽略编译器的警告

799 阅读4分钟

前言

国庆前一天,我在调试代码时产生了一个崩溃。 通过gdb定位,发现一个函数的返回值被截断了,函数的返回值是一个地址,在调用函数的地方只剩下低4个字节的值了。

1ret is 0x7fef44402a70
2get ret is 0x44402a70

然后访问返回的地址导致了程序崩溃。奇怪的是,我之前用自己的demo验证过了的,而且可以正常运行。

究根结底

上网查了一下,找到了一个类似的问题:

在C工程中,一个64位系统中如果一个文件中的某个函数A调用另外一个文件中的函数B,但是A文件中没有包含B的声明,gcc可以编译通过,但是如果B函数的返回类型为指针,在64位系统应该返回64bit地址,实际上函数A调用B得到的B的返回指针却是32bit,高32bit被截断。

主要就是gcc编译器对本函数调用未声明的函数,都强制将其返回值类型转为int类型,int在64bit系统中占4个字节,这样指针类型的返回值就会出现截断现象!

我写了一个简单的demo复现了这个问题:

 1// getData.c
 2
 3#include "common.h"
 4
 5struct data *getData()
 6{
 7    struct data *ret = (struct data *)malloc(sizeof(struct data));
 8    printf("ret is %p\r\n", ret);
 9    return ret;
10}
11
12
13// truncret.c
14#include "common.h"
15
16void showData(struct data *pdata) {
17    printf("data is a = %d, b = %d \r\n", pdata->a, pdata->b);
18}
19
20int main()
21{
22    struct data *ret = getData();
23    printf("get ret is %p\r\n", ret);
24    ret->a = 100;
25    ret->b = 200;
26    showData(ret);
27
28    free(ret);
29
30
31    return 0;    
32}

直接把两个文件一起编译生成目标文件,可以看到有警告:

 1gcc truncret.c getData.c -o a
 2truncret.c:11:24: warning: implicit declaration of function 'getData' is invalid in C99
 3      [-Wimplicit-function-declaration]
 4    struct data *ret = getData();
 5                       ^
 6truncret.c:11:18: warning: incompatible integer to pointer conversion initializing 'struct data *' with an
 7      expression of type 'int' [-Wint-conversion]
 8    struct data *ret = getData();
 9                       ^
102 warnings generated.

成功生成了目标文件,但是执行时段错误了。

1Keep:sysrepo keep$ ./a
2ret is 0x7faf01402a70
3get ret is 0x1402a70
4Segmentation fault: 11

这种情况, 我们只需要在调用之前声明一下函数原型就可以了。

 1// truncret.c
 2#include "common.h"
 3
 4void showData(struct data *pdata) {
 5    printf("data is a = %d, b = %d \r\n", pdata->a, pdata->b);
 6}
 7
 8extern struct data *getData();
 9int main()
10{
11    struct data *ret = (struct data *)getData();
12    printf("get ret is %p\r\n", ret);
13    ret->a = 100;
14    ret->b = 200;
15    showData(ret);
16
17    free(ret);
18
19
20    return 0;    
21}

再次编译没有报错, 执行正常。

1Keep:sysrepo keep$ gcc truncret.c getData.c -o a
2Keep:sysrepo keep$ ./a
3ret is 0x7f8560c02a70
4get ret is 0x7f8560c02a70
5data is a = 100, b = 200 

后话

在编译的过程中,不要忽略任何细微的警告。实际在很多项目中,为了使编译的结果更稳定可靠,编译的时候就是要求做到告警清零的。gcc使用选项-Werror,只要有任一一个警告,编译都会报错。

 1Keep:sysrepo keep$ gcc truncret.c getData.c -o a -Werror
 2truncret.c:11:24error: implicit declaration of function 'getData' is invalid in C99
 3      [-Werror,-Wimplicit-function-declaration]
 4    struct data *ret = getData();
 5                       ^
 6truncret.c:11:18error: incompatible integer to pointer conversion initializing 'struct data *' with an expression
 7      of type 'int' [-Werror,-Wint-conversion]
 8    struct data *ret = getData();
 9                 ^     ~~~~~~~~~
102 errors generated.

这里很奇怪的是,为什么我的demon没有出问题呢。 通过实际跟踪,主要是由于返回值和截断的值是一致的,导致在demo调试的过程中,问题没有被发现,在最后集成的时候才爆出问题。

更多

很快,今天就3号了,祝大家国庆节快乐!

国庆这几天,由于福建还是有疫情的,尽量就不出去浪了。前面两天给自己放了一个小假,不考虑工作和技术。

主要就是看了一下电视剧《功勋》,电视还是拍的很不错了,让我们缅怀一下那些共和国的英雄们。

电视剧不单单告诉我们,和平美好的生活来之不易,还告诉我们一个道理everything is possible

在遇到挫折和困难的时候,一定要坚持下去,才会有成功的可能,当然,成功不是简单的坚持就可以获得的,需要我们动脑,需要有坚实的基础。

虽然说,只要功夫深,铁杵磨成针。但是有好的方法和条件,成功的概率更大,时间才能更短。做事情不能简单的愚公移山,遇到大山的时候,如果你有好技术和方案,直接挖隧道或者搭桥绕过去,离成功才会更近。

工作也是一样的,平常多学习一些知识,拓宽知识面。在遇到问题的时候,可以更轻松的迎刃而解。

加油,在IT这个技术瞬息万变的行业里,只要加强学习,认真巩固总结,我相信没有什么所谓的35岁危机可以吓到我们的。工程师嘛,就得攻城略地才有价值,活到老学到老。


行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei

个人博客: fishmwei.gitee.io/