内容简介:最近要将整个项目的代码从原先的只支持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 *指针格外小心
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML Dog
Patrick Griffiths / New Riders Press / 2006-11-22 / USD 49.99
For readers who want to design Web pages that load quickly, are easy to update, accessible to all, work on all browsers and can be quickly adapted to different media, this comprehensive guide represen......一起来看看 《HTML Dog》 这本书的介绍吧!