参考 Overloading Functions in C函数重载大家都知道 C 等支持面向对象的语言支持函数重载那么编译器是如何辨别这些函数的呢CPP如何实现重载C 实现函数重载很大程度上依赖与编译器对函数名的 Mangling(损坏破坏)即 C 的源代码被编译后同名的重载函数名字会被破坏一般是在原函数名前后加上特定的字符串g编译器中通过在函数名后面添加参数的后缀以区分不同重载函数然后在调用的时候根据参数的不同选择合适的函数如下代码说明了编译器是如何处理普通函数重载的几个同名的重载函数仍然是不同的函数编译后应该有不同的地址那么它们是如何区分的呢我们自然想到函数接口的两个要素: 参数与返回值。如果同名函数的参数不同(包括类型、顺序不同),那么容易区别出它们是不同的函数。如果同名函数仅仅是返回值类型不同,有时可以区分,有时却不能。例如:voidFunction(void);intFunction(void);上述两个函数,第一个没有返回值,第二个的返回值是 int 类型。如果这样调用函数:int x Function();则可以判断出 Function 是第二个函数。问题是在 C/C 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个 Function 函数被调用。所以只能靠参数而不能靠返回值类型的不同来区分重载函数。编译器根据参数为每个重载函数产生不同的内部标识符。不同的编译器可能产生不同风格的内部标识符如果 C程序要调用已经被编译后的 C 函数,该怎么办?假设某个 C 函数的声明如下:voidfoo(intx,inty);该函数被 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 直接引用这些头文件。CPP函数的命名规则比如如下的C代码#includeusingnamespacestd;intfunc(void) {coutfunc without parameters endl; }intfunc(intia) {coutfunc with one int parameter: endl;cout ia endl; }intfunc(intia,floatfb) {coutfunc with one int parameter and one float parameter endl;cout ia endl;cout fb endl; }intmain() { func(); func(5); func(5,5.0); }我们可以通过g的-S指令将我们的程序编译成汇编main: .LFB1052: pushq%rbp.seh_pushreg%rbpmovq%rsp,%rbp.seh_setframe%rbp,0subq$32,%rsp.seh_stackalloc32.seh_endprologue call __main call _Z4funcv movl$5,%ecxcall _Z4funci movss .LC3(%rip),%xmm1movl$5,%ecxcall _Z4funcif movl$0,%eaxaddq$32,%rsppopq%rbpret .seh_endproc .def __tcf_0; .scl3; .type32; .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 函数就是一个非常好的例子C语言的函数不支持重载#includeintfunc(void) {printf(func in C...\n); }intmain() { func(); }编译成汇编可以看到main: pushq%rbp.seh_pushreg%rbpmovq%rsp,%rbp.seh_setframe%rbp,0subq$32,%rsp.seh_stackalloc32.seh_endprologue call __main call func movl$0,%eaxaddq$32,%rsppopq%rbpret .seh_endproc .identGCC: (tdm64-1) 5.1.0.def puts; .scl2; .type32; .endef编译器处理后函数命名仍然是func没有加上对参数的识别。C语言实现函数重载 可变参数但是在很多情况下利用可变参数可以实现 C 语言的函数重载的POSIX 接口中定义的 open 函数就是一个非常好的例子#include#include#includeintopen(constchar*pathname,intflags);intopen(constchar*pathname,intflags, mode_t mode);以下是一个简单的例子”重载”了两个函数第一个函数是两个参数第二个函数带了三个函数其中第三个函数是可选的ANSI C 标准中有可变参数的概念可以通过一组宏实现函数描述col 3 isright-alignedva_list arg_ptr定义一个可变参数列表指针va_start(arg_ptr, argN)让arg_ptr指向参数argNva_arg(arg_ptr, type)返回类型为type的参数指针,并指向下一个参数va_copy(dest, src)拷贝参数列表指针,src-dest,va_end(arg_ptr)清空参数列表并置参数指针arg_ptr无效。每个va_start()必须与一个va_end()对应#include#includeintgetMax(intn, ...) { va_list va; va_start(va,n);//init va, pointing to the first argumentinttmp,smax-1;inti;for(i0;iint);//get thenextargument, the type isintif(tmpsmax) smaxtmp; } va_end(va);returnsmax; }intmain() {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#includetypedefstruct_int_param {intparam1;intparam2; }INT_PARAM;typedefstruct_double_param_ {doubleparam1;doubleparam2; }DOUBLE_PARAM;typedefvoid* (*ADDFUNC)(void*);void* int_add_func(void* wParam) { INT_PARAM* lParam (INT_PARAM*)wParam;intres lParam-param1 lParam-param2;printf(result %d\n, res); }void* double_add_func(void* wParam) { DOUBLE_PARAM* lParam (DOUBLE_PARAM*)wParam;doubleres lParam-param1 lParam-param2;printf(result %f\n, res); }void* add_func(ADDFUNC f,void* wParam) {returnf(wParam); }intmain() { INT_PARAM val1 {10,20}; DOUBLE_PARAM val2 {30.5,40.5}; add_func(int_add_func, val1); add_func(double_add_func, val2);return0; }实现参数类型的重载这主要是利用了 GCC 的内置函数__builtin_types_compatible_p()和__builtin_choose_expr(),例如structs1 {inta;intb;doublec; };structs2 {longlonga;longlongb; };voidgcc_overload_s1(structs1 s) { printf(Got a struct s1: %d %d %f\n, s.a, s.b, s.c); }voidgcc_overload_s2(structs2 s) { printf(Got a struct s2: %lld %lld\n, s.a, s.b); }// warning: dereferencing type-punned pointer will break strict-aliasing rules#definegcc_overload(A)\__builtin_choose_expr(__builtin_types_compatible_p(typeof(A),structs1),\ gcc_overload_s1(*(structs1 *)A),\ __builtin_choose_expr(__builtin_types_compatible_p(typeof(A),structs2),\ gcc_overload_s2(*(structs2 *)A),(void)0))或者一个更高级的写法voidgcc_type_overload_aux(inttypeval, ...) {switch(typeval) {case1: { va_list v; va_start(v, typeval);structs1 s va_arg(v,structs1); va_end(v); gcc_overload_s1(s);break; }case2: { va_list v; va_start(v, typeval);structs2 s va_arg(v,structs2); va_end(v); gcc_overload_s2(s);break; }default: { printf(Invalid type to gcc_type_overload()\n); exit(1); } } }#definegcc_type_overload(A)\gcc_type_overload_aux(\ __builtin_types_compatible_p(typeof(A),structs1) *1\ __builtin_types_compatible_p(typeof(A),structs2) *2\ , A)另外两种用 C 实现函数重载的方法可以是利用宏和预处理以及函数指针只不过具体的重载方式也要根据特定的应用场景来决定。最终版#include#include#includevoidva_overload2(intp1,intp2) {printf(va_overload2 %d %d\n, p1, p2); }voidva_overload3(intp1,intp2,intp3) {printf(va_overload3 %d %d %d\n, p1, p2, p3); }staticvoidva_overload(intp1,intp2, ...) {if(p2 7) { va_list v; va_start(v, p2);intp3 va_arg(v,int); va_end(v); va_overload3(p1, p2, p3);return; } va_overload2(p1, p2); }staticvoidprint_nt_strings(constchar*s, ...) { va_list v; va_start(v, s);/* Stop on NULL */while(s) {printf(%s, s);/* Grab next parameter */s va_arg(v,constchar*); } 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)staticvoidprint_strings2(intcount, ...) {inti; va_list v; va_start(v, count);for(i 0; i count; i) {/* Grab next parameter print it */constchar*s va_arg(v,constchar*);printf(%s, s); } va_end(v); }#define print_strings(...)\print_strings2(COUNT_PARMS(__VA_ARGS__), __VA_ARGS__)voidcount_overload1(intp1) {printf(One param: %d\n, p1); }voidcount_overload2(double*p1,constchar*p2) {printf(Two params: %p (%f) %s\n, p1, *p1, p2); }voidcount_overload3(intp1,intp2,intp3) {printf(Three params: %c %d %d\n, p1, p2, p3); }voidcount_overload_aux(intcount, ...) { va_list v; va_start(v, count);switch(count) {case1: {intp1 va_arg(v,int); count_overload1(p1);break; }case2: {double*p1 va_arg(v,double*);constchar*p2 va_arg(v,constchar*); count_overload2(p1, p2);break; }case3: {intp1 va_arg(v,int);intp2 va_arg(v,int);intp3 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__)voidcpp_overload1(intp1) {printf(CPP One param: %d\n, p1); }voidcpp_overload2(double*p1,constchar*p2) {printf(CPP Two params: %p (%f) %s\n, p1, *p1, p2); }voidcpp_overload3(intp1,intp2,intp3) {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)voidcpp_default2(intx,constchar*s) {printf(Got %d %s\n, x, s); }#define cpp_default(...)\CAT(cpp_default, COUNT_PARMS(__VA_ARGS__))(__VA_ARGS__)voidsizeof_overload_float(floatf) {printf(Got float %f\n, f); }voidsizeof_overload_double(doubled) {printf(Got double %f\n, d); }voidsizeof_overload_longdouble(longdoubleld) {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(longdouble))?sizeof_overload_longdouble(A):(void)0)structs1 {inta;intb;doublec; };structs2 {longlonga;longlongb; };voidgcc_overload_s1(structs1 s) {printf(Got a struct s1: %d %d %f\n, s.a, s.b, s.c); }voidgcc_overload_s2(structs2 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),structs1),\ gcc_overload_s1(*(structs1 *)A),\ __builtin_choose_expr(__builtin_types_compatible_p(typeof(A),structs2),\ gcc_overload_s2(*(structs2 *)A),(void)0))voidgcc_type_overload_aux(inttypeval, ...) {switch(typeval) {case1: { va_list v; va_start(v, typeval);structs1 s va_arg(v,structs1); va_end(v); gcc_overload_s1(s);break; }case2: { va_list v; va_start(v, typeval);structs2 s va_arg(v,structs2); 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),structs1) *1\ __builtin_types_compatible_p(typeof(A),structs2) *2\ , A)voidprint_type(intt, va_list *v) {switch(t) {case1: {intp va_arg(*v,int);printf(int :%d\n, p);break; }case2: {longlongp va_arg(*v,longlong);printf(long long :%lld\n, p);break; }case3: {doublep va_arg(*v,double);printf(double :%f\n, p);break; }case4: {longdoublep va_arg(*v,longdouble);printf(long double :%Lf\n, p);break; }default: {printf(Unknown type\n);exit(1); } } }voidparam_lister1_aux(intt1, ...) { va_list v; va_start(v, t1);printf(1st param:); print_type(t1, v); va_end(v); }voidparam_lister2_aux(intt1, ...) {intt2; 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); }voidparam_lister3_aux(intt1, ...) {intt2, 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); }voidparam_lister4_aux(intt1, ...) {intt2, 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),longlong) *2\ __builtin_types_compatible_p(typeof(A),double) *3\ __builtin_types_compatible_p(typeof(A),longdouble) *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__)intmain() { param_lister(1); param_lister(1,2.0,3,6.0);return0; }