前言
最近要将整个项目的代码从原先的只支持32位变成同时支持32位和64位,这个过程中遇到一个很不容易定位的挂死问题,花了不少时间才定位解决,因此分享给大家。
32位和64位代码区别
在分享之前,需要了解一下32位和64位程序代码有何区别,它的主要区别体现在某些数据类型的占用字节大小的不同:
数据类型32位64位long4字节8字节unsigned long4字节8字节指针4字节8字节size_t4字节8字节ssize_t4字节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#includeint main(void){ void *p = NULL; //打印p的地址 printf("%p