问题描述及定位
上周做测试时遇到一个比较奇怪的问题,也不算是bug,只能说是c语言的缺陷。具体是这样的,我们自己封装了一个字符串的append函数,参数是可变参数列表,最后一个参数必须是NULL结尾,原型如下:
1 | void append(std::string& builder, const char* arg0,...) |
我们的习惯用法是:
1 | string dstStr; |
但是,在不同编译环境下,编译之后的可执行文件在执行到最后一个NULL参数时,会继续执行append,导致coredump。gdb分析具体堆栈信息:
查看库函数:/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/basic_string.h:871
/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/char_traits.h:263
可以发现,在求字符串长度时导致coredump,输出该字符串时,显示地址越界。
使用gdb一步一步调试时发现,NULL在做为参数传递给append时,被转成了一个非0的值,导致本该遇到NULL就终止的while循环,继续运行append(NULL),导致coredump,调试信息如下:
(可以手动测试append(NULL), 会报同样的段错误,堆栈信息类似)
问题根本原因
搜索相关资料发现,在不同环境下,当sizeof(int) != sizeof(char *)时,有些编译器会将NULL当成普通的int 0(4字节)来传递,但是在va_arg中,又把0当中char *(8字节)来取值,导致出现上述while循环不会终止的问题。
You should really be passing (char*)NULL, as NULL may be defined as a plain 0 and the compiler has no way to implicitly know it’s a pointer value without a cast. This is especially important if sizeof(int) != sizeof(char *) which is not that uncommon with 64-bit implementations.
解决方法
- 在调用函数时,结尾的NULL参数前加char*做强制类型转换。类似这样:
1 | append(dstStr, "abc", str1, "bcd", str2, " ", (char *)NULL); |
- 修改while循环的终止条件,显示的使用 != NULL 条件:
1 | while ((arg_str=va_arg(arg_ptr,char*)) != NULL) |
以下是c-faq给出的使用示例:
其他类似情况
fork之后的子进程中,需要使用exec函数族时,如果需要传参数列表,也需要在NULL前加char*。比如:
1 | execl("/bin/sh", "sh", "-c", "date", (char *)0); |