遇到一个问题,在调用函数时出现错误。
有两个函数声明:
test.cpp
#include <stdarg.h>
#include <syslog.h>
void funcA( const char* format, ...); // No.1 func
void funcA( const char* format, va_list args_list); // No.2 func
int main()
{
int a = 2;
const char* str = "Hello";
char buf[10];
strcpy(buf, "test");
funcA("Call funcA: %d.", a); // Call the No.1 func
funcA("Call funcA: %s.", str); // Call the No.1 func
funcA("Call funcA: %s.", buf); // Call the No.2 func in mips-linux toolchain.
return 0;
}
void funcA( const char* format, ...)
{
va_list args_list;
va_start(args_list, format);
funcA(format, args_list);
va_end(args_list);
}
void funcA( const char* format, va_list args_list)
{
vsyslog(args_list);
}
编译上面的test.cpp文件,使用arm-linux toolchain就没有问题,都会调用到No.1函数。
但当使用mips-linux toolchain时,第三个就会调用到No.2函数,这种情况仅发生在format后面只有一个参数,且是char*变量时。
然后在调用到vsyslog这个系统函数时,程序就会卡死。
编译器在根据参数列表选择要调用的函数时,第一个参数两个函数都匹配,而第二个的话,char*就匹配到了va_list,优先于省略参数。
后面没有参数了,就选择了No.2函数进行调用。而从设计角度讲,不应该出现这样的两个函数 原型同时开发给外部,本就容易引起误解。
那为什么会如此匹配呢?
打开GCC工程的源码,来寻找一下va_list的定义。
会发现,这个类型的定义虽然由头文件stdarg.h提供,是C标准,但实际定义却是根据编译器、操作系统和ABI所决定的。在不同平台下,使用的定义也会不同。可能是个结构体,或者是个指针,或则是编译器内部实现的黑科技(black magic)。
1,#include <stdarg.h>
在gcc的工程里,有多个此同名文件。选取的是\gcc\ginclude\stdarg.h
2,在里面找到定义:
typedef __builtin_va_list __gnuc_va_list;
typedef __gnuc_va_list va_list;
3, 下载gcc的源码,在里面找到文件:gcc\config\mips\mips.cc
找到下面函数:static tree mips_build_builtin_va_list (void)
4, 就是这个函数构造了va_list类型。
在gcc工程里查找,mips架构的定义在mips.cc文件里,这段代码看不懂,但如果是类型判断引起的问题,就是MIPS架构下的这个va_list定义,以及mips编译工具链的判断造成了上面的问题。感觉走的是下面的分支,是void*的类型。
static tree
mips_build_builtin_va_list (void)
{
if (EABI_FLOAT_VARARGS_P)
{
/* We keep 3 pointers, and two offsets.
Two pointers are to the overflow area, which starts at the CFA.
One of these is constant, for addressing into the GPR save area
below it. The other is advanced up the stack through the
overflow region.
The third pointer is to the bottom of the GPR save area.
Since the FPR save area is just below it, we can address
FPR slots off this pointer.
We also keep two one-byte offsets, which are to be subtracted
from the constant pointers to yield addresses in the GPR and
FPR save areas. These are downcounted as float or non-float
arguments are used, and when they get to zero, the argument
must be obtained from the overflow region. */
tree f_ovfl, f_gtop, f_ftop, f_goff, f_foff, f_res, record;
tree array, index;
record = lang_hooks.types.make_type (RECORD_TYPE);
f_ovfl = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__overflow_argptr"),
ptr_type_node);
f_gtop = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__gpr_top"),
ptr_type_node);
f_ftop = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__fpr_top"),
ptr_type_node);
f_goff = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__gpr_offset"),
unsigned_char_type_node);
f_foff = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__fpr_offset"),
unsigned_char_type_node);
/* Explicitly pad to the size of a pointer, so that -Wpadded won't
warn on every user file. */
index = build_int_cst (NULL_TREE, GET_MODE_SIZE (ptr_mode) - 2 - 1);
array = build_array_type (unsigned_char_type_node,
build_index_type (index));
f_res = build_decl (BUILTINS_LOCATION,
FIELD_DECL, get_identifier ("__reserved"), array);
DECL_FIELD_CONTEXT (f_ovfl) = record;
DECL_FIELD_CONTEXT (f_gtop) = record;
DECL_FIELD_CONTEXT (f_ftop) = record;
DECL_FIELD_CONTEXT (f_goff) = record;
DECL_FIELD_CONTEXT (f_foff) = record;
DECL_FIELD_CONTEXT (f_res) = record;
TYPE_FIELDS (record) = f_ovfl;
DECL_CHAIN (f_ovfl) = f_gtop;
DECL_CHAIN (f_gtop) = f_ftop;
DECL_CHAIN (f_ftop) = f_goff;
DECL_CHAIN (f_goff) = f_foff;
DECL_CHAIN (f_foff) = f_res;
layout_type (record);
return record;
}
else
/* Otherwise, we use 'void *'. */
return ptr_type_node;
}
gcc中的arm.cc和aarch64.cc里面有各自的va_list的定义。
arm架构的定义注释说明,基于AAPCS ABI:
/* AAPCS \S 7.1.4 requires that va_list be a typedef for a type
defined as:
struct __va_list
{
void *__ap;
};
The C Library ABI further reinforces this definition in \S
4.1.
We must follow this definition exactly. The structure tag
name is visible in C++ mangled names, and thus forms a part
of the ABI. The field name may be used by people who
#include <stdarg.h>. */
aarch64的target的话,基于AAPCS64 ABI:
/* Implement TARGET_BUILD_BUILTIN_VA_LIST.
Return the type to use as __builtin_va_list.
AAPCS64 \S 7.1.4 requires that va_list be a typedef for a type defined as:
struct __va_list
{
void *__stack;
void *__gr_top;
void *__vr_top;
int __gr_offs;
int __vr_offs;
}; */
在某个目标环境中,如果没有实现指定的va_list的定义,则gcc认为是void*类型,在builtins.cc里定义:
/* The "standard" definition of va_list is void*. */
tree
std_build_builtin_va_list (void)
{
return ptr_type_node;
}
参考:
c - Which is the definition of va_list? - Stack Overflow