小心库函数调用的'陷阱'
下午一同事发现代码中的一处问题,问题的现象是这样的:这位同事调用了一部门基础库函数,当使用32位编译后,程序正常运行;而当使用64位编译后,系统运行dump core。让这位同事奇怪的是他所修改的程序中还有其他模块也使用了同样的基础库函数,为什么偏偏他这块儿出错呢?恰恰该程序的其他模块是我写的。
该程序调用的基础库函数大致是这样的:
typedef unsigned long my_size_t;
int my_socket_recv(my_socket_t socket, char *buf, my_size_t *len, int timeout);
而这个库函数的实现主要就是调用了系统的库函数:recv,通过man命令查到recv函数原型为:ssize_t recv(int s, void *buf, size_t len, int flags);
而my_socket_recv的实现如下:
int my_socket_recv (my_socket_t socket, char *buf, my_size_t *len, int timeout)
{
int rv;
… …
rv = recv(socket, buf, (*len), 0);
… …
}
使用gdb察看core文件,通过栈上信息看出(*len)值有问题,有经验的人可能基本就可以断定问题所在了,对了,应该类型长度不匹配的问题,导致内存访问越界。同事的代码也证实了这一点。原来我的这位同事在调用my_socket_recv时第三个参数传入一个unsigned int型的地址,而不是unsigned long型的地址,我们知道在64位编译下,long型已经升级到8个字节,而int型依旧是4个字节,而size_t也是unsigned long的typedef,所以当调用recv时,通过len指针,recv系统调用毫无顾忌的去取8个字节,而实际上在栈上只有前四个字节是合法的数据,从第5个字节开始已经是非法的了,出core也就在情理之中了。我们可以简单的模拟出这种情况,如下面的例子:
/* test.c */
#include
void print_long_int(unsigned long *i) {
printf("i is %ld\n", *i);
}
int main() {
unsigned int n = 1024;
print_long_int(&n);
return 0;
}
64位编译:
gcc -m64 -g test.c
执行a.out出core.
问题虽然解决了,但是思前想后,发现一个问题:即库函数调用暗藏的’陷阱’问题,像这样的问题实际上是基础库函数的接口设计的不够好,实际上它的正常运行依赖某些条件,而不是’无偿’的,但是这些条件又很难识别出来,特别是对于新手而言更是难上难。
想出两种办法解决:
1、不要设计像上面my_socket_recv这样的带有’隐含’条件的接口(recv系统调用接口就没有隐含条件);
2、调用接口的人不要想当然的随便传入不同类型的数据,应尽量保持实参数据类型与形参数据类型完全匹配(这块儿编译器可以帮你做检查),如果不这样,很有可能像我的这位同事一样,掉入了库函数调用的陷阱中。^_^
评论