参考 Overloading Functions in C
大家都知道 C++ 等支持面向对象的语言支持函数重载,那么编译器是如何辨别这些函数的呢?
C++ 实现函数重载很大程度上依赖与编译器对函数名的 Mangling(损坏,破坏),即 C++ 的源代码被编译后同名的重载函数名字会被破坏,一般是在原函数名前后加上特定的字符串(g++编译器中通过在函数名后面添加参数的后缀),以区分不同重载函数,然后在调用的时候根据参数的不同选择合适的函数,如下代码说明了编译器是如何处理普通函数重载的
几个同名的重载函数仍然是不同的函数,编译后应该有不同的地址,那么它们是如何区分的呢?我们自然想到函数接口的两个要素: 参数与返回值。
如果同名函数的参数不同(包括类型、顺序不同),那么容易区别出它们是不同的函数。如果同名函数仅仅是返回值类型不同,有时可以区分,有时却不能。例如:
void Function(void);
int Function (void);
上述两个函数,第一个没有返回值,第二个的返回值是 int 类型。如果这样调用函数:
int x = Function ();
则可以判断出 Function 是第二个函数。问题是在 C++/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个 Function 函数被调用。
所以只能靠参数而不能靠返回值类型的不同来区分重载函数。
编译器根据参数为每个重载函数产生不同的内部标识符。不同的编译器可能产生不同风格的内部标识符
如果 C++程序要调用已经被编译后的 C 函数,该怎么办?
假设某个 C 函数的声明如下:
void foo(int x, int y);
该函数被 C 编译器编译后在库中的名字为_foo,而 C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接。
这样一来很明显,C和C++中对函数的生成规则是不同的,由于编译后的名字不同,C++程序不能直接调用 C 函数。
但是C++是完全兼容C的,而且我们的C++程序往往在不断的调用C库,C++提供了一个 C 连接交换指定符号 extern“C”
来解决这个问题。
extern “C”
{
void foo(int x, int y);
... // 其它函数
}
或者写成
extern “C”
{
#include “myheader.h”
... // 其它 C 头文件
}
这就告诉 C++编译译器,函数 foo 是个 X库的函数,那么C++编译器应该按照C编译器的编译和链接规则来进行链接,也就是说到库中找名字_foo 而不是找_foo_int_int。
C++编译器开发商已经对 C 标准库的头文件作了 extern“C”处理,所以我们可以用#include 直接引用这些头文件。
比如如下的C++代码
#include <iostream>
using namespace std;
int func(void)
{
cout << "func without parameters" << endl;
}
int func(int ia)
{
cout << "func with one int parameter: " << endl;
cout << ia << endl;
}
int func(int ia, float fb)
{
cout << "func with one int parameter and one float parameter" << endl;
cout << ia << endl;
cout << fb << endl;
}
int main()
{
func();
func(5);
func(5, 5.0);
}
我们可以通过g++的-S指令,将我们的程序编译成汇编
main:
.LFB1052:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $32, %rsp
.seh_stackalloc 32
.seh_endprologue
call __main
call _Z4funcv
movl $5, %ecx
call _Z4funci
movss .LC3(%rip), %xmm1
movl $5, %ecx
call _Z4funcif
movl $0, %eax
addq $32, %rsp
popq %rbp
ret
.seh_endproc
.def __tcf_0; .scl 3; .type 32; .endef
.seh_proc __tcf_0
__tcf_0:
.LFB1063:
可以看到,func 的三个版本重载函数在编译后名字都被破坏了,编译器将他们重命名为了 _Z4funcv, _Z4funci, _Z4funcif, (g++ 编译器可能根据函数参数类型为函数名加上了与参数类型相关的特定后缀,如func(void) 变成了 _Z4funcv, func(int) 变成了_Z4funci, func(int, float)变成了 _Z4funcif),然后在调用各个版本的func()时,编译器根据参数类型的不同选择合适的重载函数,如调用 func() 其实是调用了 _Z4funcv, 调用 func(5, 5.0)实际上是调用了 _Z4funcif等。
但是,在很多情况下,利用可变参数可以实现 C 语言的函数重载的,POSIX 接口中定义的 open 函数就是一个非常好的例子,
#include <stdio.h>
int func(void)
{
printf("func in C...\n");
}
int main()
{
func();
}
编译成汇编可以看到
main:
pushq %rbp
.seh_pushreg %rbp
movq %rsp, %rbp
.seh_setframe %rbp, 0
subq $32, %rsp
.seh_stackalloc 32
.seh_endprologue
call __main
call func
movl $0, %eax
addq $32, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (tdm64-1) 5.1.0"
.def puts; .scl 2; .type 32; .endef
编译器处理后,函数命名仍然是func,没有加上对参数的识别。
但是,在很多情况下,利用可变参数可以实现 C 语言的函数重载的,POSIX 接口中定义的 open 函数就是一个非常好的例子,
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
以下是一个简单的例子,”重载”了两个函数,第一个函数是两个参数,第二个函数带了三个函数,其中第三个函数是可选的,
ANSI C 标准中,有可变参数的概念,可以通过一组宏实现
函数 | 描述 |
---|---|
col 3 is | right-aligned |
va_list arg_ptr | 定义一个可变参数列表指针 |
va_start(arg_ptr, argN) | 让arg_ptr指向参数argN |
va_arg(arg_ptr, type) | 返回类型为type的参数指针,并指向下一个参数 |
va_copy(dest, src) | 拷贝参数列表指针,src->dest, |
va_end(arg_ptr) | 清空参数列表,并置参数指针arg_ptr无效。每个va_start()必须与一个va_end()对应 |
#include<stdio.h>
#include<stdarg.h>
int getMax(int n, ...)
{
va_list va;
va_start(va,n); // init va, pointing to the first argument
int tmp,smax=-1;
int i;
for(i=0;i<n;i++)
{
tmp=va_arg(va,int); // get the next argument, the type is int
if(tmp>smax) smax=tmp;
}
va_end(va);
return smax;
}
int main()
{
printf("%d/n",getMax(4,9,5,2,19));
printf("%d/n",getMax(6,1,3,4,5,2,0));
}
参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈
因此,假设定义一个可变参数的函数 void f(int x, …), 通过f( x, y, z) 调用,那么,z先入栈,然后y, 然后x。 因此我们只要得到任何一个变量的地址,就可以找到其它变量的地址。
va_start(va, n) 就是让va指向n的地址。这样,后面就可以得到所有参数的值。前提是,我们必须知道每个参数的类型。在本例子中,都是int类型。
#include <stdio.h>
#include <stdlib.h>
typedef struct _int_param
{
int param1;
int param2;
}INT_PARAM;
typedef struct _double_param_
{
double param1;
double param2;
}DOUBLE_PARAM;
typedef void* (*ADDFUNC)(void*);
void* int_add_func(void* wParam)
{
INT_PARAM* lParam = (INT_PARAM*)wParam;
int res = lParam->param1 + lParam->param2;
printf("result = %d\n", res);
}
void* double_add_func(void* wParam)
{
DOUBLE_PARAM* lParam = (DOUBLE_PARAM*)wParam;
double res = lParam->param1 + lParam->param2;
printf("result = %f\n", res);
}
void* add_func(ADDFUNC f, void* wParam)
{
return f(wParam);
}
int main()
{
INT_PARAM val1 = {10, 20};
DOUBLE_PARAM val2 = {30.5, 40.5};
add_func(int_add_func, &val1);
add_func(double_add_func, &val2);
return 0;
}
这主要是利用了 GCC 的内置函数,__builtin_types_compatible_p()和__builtin_choose_expr(),
例如:
struct s1
{
int a;
int b;
double c;
};
struct s2
{
long long a;
long long b;
};
void gcc_overload_s1(struct s1 s)
{
printf("Got a struct s1: %d %d %f\n", s.a, s.b, s.c);
}
void gcc_overload_s2(struct s2 s)
{
printf("Got a struct s2: %lld %lld\n", s.a, s.b);
}
// warning: dereferencing type-punned pointer will break strict-aliasing rules
#define gcc_overload(A)\
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s1),\
gcc_overload_s1(*(struct s1 *)&A),\
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s2),\
gcc_overload_s2(*(struct s2 *)&A),(void)0))
或者一个更高级的写法:
void gcc_type_overload_aux(int typeval, ...)
{
switch(typeval)
{
case 1:
{
va_list v;
va_start(v, typeval);
struct s1 s = va_arg(v, struct s1);
va_end(v);
gcc_overload_s1(s);
break;
}
case 2:
{
va_list v;
va_start(v, typeval);
struct s2 s = va_arg(v, struct s2);
va_end(v);
gcc_overload_s2(s);
break;
}
default:
{
printf("Invalid type to 'gcc_type_overload()'\n");
exit(1);
}
}
}
#define gcc_type_overload(A)\
gcc_type_overload_aux(\
__builtin_types_compatible_p(typeof(A), struct s1) * 1\
+ __builtin_types_compatible_p(typeof(A), struct s2) * 2\
, A)
另外两种用 C 实现函数重载的方法可以是利用宏和预处理,以及函数指针,只不过具体的重载方式也要根据特定的应用场景来决定。
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void va_overload2(int p1, int p2)
{
printf("va_overload2 %d %d\n", p1, p2);
}
void va_overload3(int p1, int p2, int p3)
{
printf("va_overload3 %d %d %d\n", p1, p2, p3);
}
static void va_overload(int p1, int p2, ...)
{
if (p2 == 7)
{
va_list v;
va_start(v, p2);
int p3 = va_arg(v, int);
va_end(v);
va_overload3(p1, p2, p3);
return;
}
va_overload2(p1, p2);
}
static void print_nt_strings(const char *s, ...)
{
va_list v;
va_start(v, s);
/* Stop on NULL */
while (s)
{
printf("%s", s);
/* Grab next parameter */
s = va_arg(v, const char *);
}
va_end(v);
}
#define COUNT_PARMS2(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _, ...) _
#define COUNT_PARMS(...)\
COUNT_PARMS2(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
static void print_strings2(int count, ...)
{
int i;
va_list v;
va_start(v, count);
for (i = 0; i < count; i++)
{
/* Grab next parameter + print it */
const char *s = va_arg(v, const char *);
printf("%s", s);
}
va_end(v);
}
#define print_strings(...)\
print_strings2(COUNT_PARMS(__VA_ARGS__), __VA_ARGS__)
void count_overload1(int p1)
{
printf("One param: %d\n", p1);
}
void count_overload2(double *p1, const char *p2)
{
printf("Two params: %p (%f) %s\n", p1, *p1, p2);
}
void count_overload3(int p1, int p2, int p3)
{
printf("Three params: %c %d %d\n", p1, p2, p3);
}
void count_overload_aux(int count, ...)
{
va_list v;
va_start(v, count);
switch(count)
{
case 1:
{
int p1 = va_arg(v, int);
count_overload1(p1);
break;
}
case 2:
{
double *p1 = va_arg(v, double *);
const char *p2 = va_arg(v, const char *);
count_overload2(p1, p2);
break;
}
case 3:
{
int p1 = va_arg(v, int);
int p2 = va_arg(v, int);
int p3 = va_arg(v, int);
count_overload3(p1, p2, p3);
break;
}
default:
{
va_end(v);
printf("Invalid arguments to function 'count_overload()'");
exit(1);
}
}
va_end(v);
}
#define count_overload(...)\
count_overload_aux(COUNT_PARMS(__VA_ARGS__), __VA_ARGS__)
void cpp_overload1(int p1)
{
printf("CPP One param: %d\n", p1);
}
void cpp_overload2(double *p1, const char *p2)
{
printf("CPP Two params: %p (%f) %s\n", p1, *p1, p2);
}
void cpp_overload3(int p1, int p2, int p3)
{
printf("CPP Three params: %c %d %d\n", p1, p2, p3);
}
#define CAT(A, B) CAT2(A, B)
#define CAT2(A, B) A ## B
#define cpp_overload(...)\
CAT(cpp_overload, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)
#define cpp_default1(A) cpp_default2(A, "default string")
void cpp_default2(int x, const char *s)
{
printf("Got %d %s\n", x, s);
}
#define cpp_default(...)\
CAT(cpp_default, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)
void sizeof_overload_float(float f)
{
printf("Got float %f\n", f);
}
void sizeof_overload_double(double d)
{
printf("Got double %f\n", d);
}
void sizeof_overload_longdouble(long double ld)
{
printf("Got long double %Lf\n", ld);
}
#define sizeof_overload(A)\
((sizeof(A) == sizeof(float))?sizeof_overload_float(A):\
(sizeof(A) == sizeof(double))?sizeof_overload_double(A):\
(sizeof(A) == sizeof(long double))?sizeof_overload_longdouble(A):(void)0)
struct s1
{
int a;
int b;
double c;
};
struct s2
{
long long a;
long long b;
};
void gcc_overload_s1(struct s1 s)
{
printf("Got a struct s1: %d %d %f\n", s.a, s.b, s.c);
}
void gcc_overload_s2(struct s2 s)
{
printf("Got a struct s2: %lld %lld\n", s.a, s.b);
}
// warning: dereferencing type-punned pointer will break strict-aliasing rules
#define gcc_overload(A)\
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s1),\
gcc_overload_s1(*(struct s1 *)&A),\
__builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s2),\
gcc_overload_s2(*(struct s2 *)&A),(void)0))
void gcc_type_overload_aux(int typeval, ...)
{
switch(typeval)
{
case 1:
{
va_list v;
va_start(v, typeval);
struct s1 s = va_arg(v, struct s1);
va_end(v);
gcc_overload_s1(s);
break;
}
case 2:
{
va_list v;
va_start(v, typeval);
struct s2 s = va_arg(v, struct s2);
va_end(v);
gcc_overload_s2(s);
break;
}
default:
{
printf("Invalid type to 'gcc_type_overload()'\n");
exit(1);
}
}
}
#define gcc_type_overload(A)\
gcc_type_overload_aux(\
__builtin_types_compatible_p(typeof(A), struct s1) * 1\
+ __builtin_types_compatible_p(typeof(A), struct s2) * 2\
, A)
void print_type(int t, va_list *v)
{
switch(t)
{
case 1:
{
int p = va_arg(*v, int);
printf("int :%d\n", p);
break;
}
case 2:
{
long long p = va_arg(*v, long long);
printf("long long :%lld\n", p);
break;
}
case 3:
{
double p = va_arg(*v, double);
printf("double :%f\n", p);
break;
}
case 4:
{
long double p = va_arg(*v, long double);
printf("long double :%Lf\n", p);
break;
}
default:
{
printf("Unknown type\n");
exit(1);
}
}
}
void param_lister1_aux(int t1, ...)
{
va_list v;
va_start(v, t1);
printf("1st param:");
print_type(t1, &v);
va_end(v);
}
void param_lister2_aux(int t1, ...)
{
int t2;
va_list v;
va_start(v, t1);
printf("1st param:");
print_type(t1, &v);
t2 = va_arg(v, int);
printf("2nd param:");
print_type(t2, &v);
va_end(v);
}
void param_lister3_aux(int t1, ...)
{
int t2, t3;
va_list v;
va_start(v, t1);
printf("1st param:");
print_type(t1, &v);
t2 = va_arg(v, int);
printf("2nd param:");
print_type(t2, &v);
t3 = va_arg(v, int);
printf("3rd param:");
print_type(t3, &v);
va_end(v);
}
void param_lister4_aux(int t1, ...)
{
int t2, t3, t4;
va_list v;
va_start(v, t1);
printf("1st param:");
print_type(t1, &v);
t2 = va_arg(v, int);
printf("2nd param:");
print_type(t2, &v);
t3 = va_arg(v, int);
printf("3rd param:");
print_type(t3, &v);
t4 = va_arg(v, int);
printf("4th param:");
print_type(t4, &v);
va_end(v);
}
#define TYPENUM(A)\
__builtin_types_compatible_p(typeof(A), int) * 1\
+ __builtin_types_compatible_p(typeof(A), long long) * 2\
+ __builtin_types_compatible_p(typeof(A), double) * 3\
+ __builtin_types_compatible_p(typeof(A), long double) * 4
#define param_lister1(A)\
param_lister1_aux(TYPENUM(A), A)
#define param_lister2(A, B)\
param_lister2_aux(TYPENUM(A), A, TYPENUM(B), B)
#define param_lister3(A, B, C)\
param_lister3_aux(TYPENUM(A), A, TYPENUM(B), B, TYPENUM(C), C)
#define param_lister4(A, B, C, D)\
param_lister4_aux(TYPENUM(A), A, TYPENUM(B), B, TYPENUM(C), C, TYPENUM(D), D)
#define param_lister(...)\
CAT(param_lister, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)
int main()
{
param_lister(1);
param_lister(1, 2.0, 3, 6.0);
return 0;
}