0%

C语言中varargs引起的一个小bug

问题描述及定位

上周做测试时遇到一个比较奇怪的问题,也不算是bug,只能说是c语言的缺陷。具体是这样的,我们自己封装了一个字符串的append函数,参数是可变参数列表,最后一个参数必须是NULL结尾,原型如下:

1
2
3
4
5
6
7
8
9
10
11
void append(std::string& builder, const char* arg0,...)
{
char* arg_str;
builder.append(arg0);
va_list arg_ptr;
va_start(arg_ptr, arg0);
while ((arg_str=va_arg(arg_ptr,char*))){
builder.append(arg_str);
}
va_end(arg_ptr);
}

我们的习惯用法是:

1
2
string dstStr;
append(dstStr, "abc", str1, "bcd", str2, " ", NULL);

但是,在不同编译环境下,编译之后的可执行文件在执行到最后一个NULL参数时,会继续执行append,导致coredump。gdb分析具体堆栈信息:

stack1

查看库函数:/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/basic_string.h:871

basic_string

/usr/lib/gcc/x86_64-redhat-linux/4.4.6/../../../../include/c++/4.4.6/bits/char_traits.h:263

char_traits

可以发现,在求字符串长度时导致coredump,输出该字符串时,显示地址越界。

使用gdb一步一步调试时发现,NULL在做为参数传递给append时,被转成了一个非0的值,导致本该遇到NULL就终止的while循环,继续运行append(NULL),导致coredump,调试信息如下:

gdb

(可以手动测试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给出的使用示例:

vstrcat

其他类似情况

fork之后的子进程中,需要使用exec函数族时,如果需要传参数列表,也需要在NULL前加char*。比如:

1
execl("/bin/sh", "sh", "-c", "date", (char *)0);

参考

如果对您有帮助