0%

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

前言

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

1
2
ret is 0x7fef44402a70
get 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// getData.c

#include "common.h"

struct data *getData()
{
struct data *ret = (struct data *)malloc(sizeof(struct data));
printf("ret is %p\r\n", ret);
return ret;
}


// truncret.c
#include "common.h"

void showData(struct data *pdata) {
printf("data is a = %d, b = %d \r\n", pdata->a, pdata->b);
}

int main()
{
struct data *ret = getData();
printf("get ret is %p\r\n", ret);
ret->a = 100;
ret->b = 200;
showData(ret);

free(ret);


return 0;
}

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25


// truncret.c
#include "common.h"

void showData(struct data *pdata) {
printf("data is a = %d, b = %d \r\n", pdata->a, pdata->b);
}

extern struct data *getData();
int main()
{
struct data *ret = (struct data *)getData();
printf("get ret is %p\r\n", ret);
ret->a = 100;
ret->b = 200;
showData(ret);

free(ret);


return 0;
}


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

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

后话

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

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

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

更多

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

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

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

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

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

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

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

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

行动,才不会被动!

欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。