前言
国庆前一天,我在调试代码时产生了一个崩溃。 通过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:24: error: implicit declaration of function 'getData' is invalid in C99
3 [-Werror,-Wimplicit-function-declaration]
4 struct data *ret = getData();
5 ^
6truncret.c:11:18: error: 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/