内容简介:最近要将整个项目的代码从原先的只支持32位变成同时支持32位和64位,这个过程中遇到一个很不容易定位的挂死问题,花了不少时间才定位解决,因此分享给大家。在分享之前,需要了解一下32位和64位程序代码有何区别,它的主要区别体现在某些数据类型的占用字节大小的不同:这些是主要的差别。
最近要将整个项目的代码从原先的只支持32位变成同时支持32位和64位,这个过程中遇到一个很不容易定位的挂死问题,花了不少时间才定位解决,因此分享给大家。
32位和64位代码区别
在分享之前,需要了解一下32位和64位程序代码有何区别,它的主要区别体现在某些数据类型的占用字节大小的不同:
数据类型 | 32位 | 64位 |
---|---|---|
long | 4字节 | 8字节 |
unsigned long | 4字节 | 8字节 |
指针 | 4字节 | 8字节 |
size_t | 4字节 | 8字节 |
ssize_t | 4字节 | 8字节 |
这些是主要的差别。
那么为什么要切64位呢?原因也很简单,32位寻址范围有限,能使用的最大内存也是非常有限的,因此需要使其能够支持64位,这个过程需要修改编译工程,编译第三方库为64位,修改代码等等。当然这些都不是本文的重点,本文仅介绍遇到的这个典型的问题。
问题描述
由于项目本身涉及的系统比较复杂,因此简单分享一下定位过程,下一节将通过简洁的示例程序来说明。
问题现象:向服务器发送一条操作指令后直接挂死
分析解决过程简化为如下步骤:
- 查看日志以及coredump信息, 初步定位挂死的位置
- 发现挂死在停止定时器的位置
- 32位程序正常,而64位异常,因此 和32位与64位的差别有关
- 怀疑传入定时器数据有问题,编写小demo, 排除传入数据问题
- 编译可调试版本,加入-g参数
- 跟踪调试,发现最终挂在了一个动态库中
- 设置gdb源码路径,以便调试跟踪动态库
- 通过gdb观察传入指针,在访问指针时,出现错误,提示访问非法内存
- 打印传入定时器指针地址,发现异常,地址开头4字节为全f,不正常,因此怀疑该指针最开始就已经出问题
- 跟踪启动定时器部分,动态库接口返回的地址值,就已经异常了,但是跟踪到动态库接口内部,发现返回的结果是正常的8字节地址值,排除定时器接口的问题
- 最终可以确定,在调用动态库接口时,虽然返回的是8字节地址,但是赋给外部变量时,就 被截断 了
- 换项目中的另外一个进程调试demo发现,编译时出现错误,提示函数没有声明
- 于是加上声明之后编译通过,但并没有出现挂死的问题
- 随即继续跟踪原项目出问题的进程,发现同样这些接口都没有外部声明,再加上另外一个进程的警告信息,提示有int往指针强转,因此怀疑和函数的声明有关。
最终确实如此。
具体是为什么呢?
简化示例
示例代码分别放在main.c和test.c中,main.c内容如下:
//main.c //公众号编程珠玑 #include<stdio.h> #include<stdlib.h> int main(void) { void *p = NULL; //打印p的地址 printf("%p\n",&p); //为p赋值 p = testFun(); printf("%p\n",p); //释放内存 free(p); p = NULL; return 0; }
test.c的内容如下:
//test.c #include<stdio.h> #include<stdlib.h> void *testFun() { //申请内存需要足够大,能方便达到本文的示例效果 void *p = (void*)malloc(1024*1024*10); if(NULL == p) { printf("malloc failed\n"); } printf("malloc success,p = %p\n",p); return p; }
上面两段代码再简单不过,testFun在函数中申请一段内存,并返回。而main函数通过调用testFun,将地址值返回给p,并打印p的地址值。
编译运行:
$ gcc -o main main.c test.c $ ./main 0x7ffef59d4230 malloc success,p = 0x7f193ec5f010 0x3ec5f010 Segmentation fault (core dumped)
从运行结果中,我们可以发现以下几个事实:
- 64位程序地址为8字节
- testFun内部申请到的内存地址值是占用8字节的值
- main函数中的p的地址值为4字节
- 返回值被截断了
也就是和我们预期的结果完全不一样。我们逐步分析,到底是为什么。
特别说明:
如果赋值那一行改成下面这样
p = (void*)testFun();
运行结果中如下:
0x7ffd5a75dbe0 malloc success,p = 0x7fc6fb5ac010 0xfffffffffb5ac010 Segmentation fault (core dumped)
其实看到8字节的前面4字节都是f,就可以判断这个地址是非法的了。为什么?(提示:程序地址空间分布)。
为什么coredump?
这个问题很明显,因为申请内存得到的地址值与释放内存的地址不是同一个,因此导致coredump(coredump的查看可参考《 linux常用命令-开发调试篇 》中的gdb部分)。
为什么地址值被截断?
在解释这个之前,我们先看一个简单的示例程序:
//testReturn.c #include<stdio.h> test() { printf("test function\n"); return 0; } int main(void) { test(); return 0; }
编译:
$ gcc -o test testReturn.c testReturn.c:2:1: warning: return type defaults to ‘int’ [-Wimplicit-int] test() ^
我们在编译的时候出现了一个警告,提示test函数没有返回值,会默认返回值为int。
也就是说, 如果函数实际有返回值,但是函数返回值类型却没有指明,编译器会将其默认为int 。
实际上前面的示例程序在编译的时候就有警告:
main.c: In function ‘main’: main.c:11:9: warning: implicit declaration of function ‘testFun’ [-Wimplicit-function-declaration] p = testFun(); ^ main.c:11:7: warning: assignment makes pointer from integer without a cast [-Wint-conversion] p = testFun(); ^
两个警告的意思分别为:
- testFun没有声明
- 尝试从整形转换成指针
第一个警告很容易理解,虽然定义了testFun函数,但是在main函数中并没有声明。因此对mian函数来说,它在编译阶段(关于编译阶段,可参考《 hello程序是如何变成可执行文件的 》),“看不到”testFun,因此会默认为其返回值为int。而正因如此,就有了第二个警告,提示从整型转换成指针。
到此其实也就真相大白了。既然testFun的返回值被编译器默认为int,返回一个8字节的指针类型,而返回值却是int,自然就会被截断了。
如何解决
既然知道原因所在,那么如何解决呢?这里提供两种方式。
- extern声明
- 在头文件中声明,调用者包含该头文件
按照第一种方式,在main.c中增加一行声明:
extern void *testFun();
运行结果:
0x7fffee1bd7b0 malloc success,p = 0x7fcafef2e010 0x7fcafef2e010
第二种方式,增加test.h,内容为testFun的声明:
//test.h #include<stdio.h> #include<stdlib.h> void *testFun();
main.c包含test.h头文件:
//main.c //公众号编程珠玑 #include"test.h" int main(void) { void *p = NULL; //打印p的地址 printf("%p\n",&p); //为p赋值 p = testFun(); printf("%p\n",p); //释放内存 free(p); p = NULL; return 0; }
test.c修改如下:
//test.c #include"test.h" void *testFun() { void *p = (void*)malloc(1024*1024*10); if(NULL == p) { printf("malloc failed\n"); } printf("malloc success,p = %p\n",p); return p; }
以上两种方式都可解决前面的问题。
而32位程序为什么正常?相信你已经有了答案。
总结
由于对出现问题的程序代码不熟悉,加上其编译工程充斥着大量的警告而没有处理,以及涉及动态库,导致这个引起挂死问题的罪魁祸首没有提前暴露处出来。而问题的根本原因我们也清楚了,就是因为调用函数前没有声明。本文总结如下:
- 不要忽略任何一个警告,除非你非常清楚地知道自己在做什么
- 在头文件中声明函数,并提供给调用者
- 函数使用前进行声明
- 问题长期定位不出来时,休息一下
- 尽量编写通用性代码
- 非必要时不强转
- 使用void *指针格外小心
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- CSS 来实现多行文字截断
- perl – 截断stdin行长度?
- 00截断之追本溯源
- 技术问题分析-报文截断(11.20)
- 技术问题分析-报文截断02(11.21)
- 技术问题分析-报文截断03(11.22)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。