如何实现 C 的函数重载
大家都知道 C++ 等面向对象的语言支持函数重载,C++ 实现函数重载很大程度上依赖与编译器对函数名的 Mangling(损坏,破坏),即 C++ 的源代码被编译后同名的重载函数名字会被破坏,一般是在原函数名前后加上特定的字符串,以区分不同重载函数,然后在调用的时候根据参数的不同选择合适的函数,如下代码说明了编译器是如何处理普通函数重载的:
#include <iostream>
usingnamespacestd;
intfunc(void)
{
cout<<"func without parameters"<<endl;
}
intfunc(intia)
{
cout<<"func with one int parameter:"<<endl;
cout<< ia <<endl;
}
intfunc(intia,floatfb)
{
cout<<"func 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 ./t.cc
编译后生成汇编代码可能如下:
.file"t.cc"
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.section .rodata
.LC0:
.string"func without parameters"
.text
.globl _Z4funcv
.type _Z4funcv, @function
_Z4funcv:
.LFB966:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset8
.cfi_offset5, -8
movl %esp, %ebp
.cfi_def_cfa_register5
subl $24, %esp
movl $.LC0,4(%esp)
movl $_ZSt4cout, (%esp)
call_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_,4(%esp)
movl %eax, (%esp)
call_ZNSolsEPFRSoS_E
leave
.cfi_restore5
.cfi_def_cfa4,4
ret
.cfi_endproc
.LFE966:
.size _Z4funcv, .-_Z4funcv
.section .rodata
.LC1:
.string"func with one int parameter"
.text
.globl _Z4funci
.type _Z4funci, @function
_Z4funci:
.LFB967:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset8
.cfi_offset5, -8
movl %esp, %ebp
.cfi_def_cfa_register5
subl $24, %esp
movl $.LC1,4(%esp)
movl $_ZSt4cout, (%esp)
call_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl8(%ebp), %edx
movl %edx,4(%esp)
movl %eax, (%esp)
call_ZNSolsEi
movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_,4(%esp)
movl %eax, (%esp)
call_ZNSolsEPFRSoS_E
leave
.cfi_restore5
.cfi_def_cfa4,4
ret
.cfi_endproc
.LFE967:
.size _Z4funci, .-_Z4funci
.section .rodata
.align4
.LC2:
.string"func with one int parameter and one float parameter"
.text
.globl _Z4funcif
.type _Z4funcif, @function
_Z4funcif:
.LFB968:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset8
.cfi_offset5, -8
movl %esp, %ebp
.cfi_def_cfa_register5
subl $24, %esp
movl $.LC2,4(%esp)
movl $_ZSt4cout, (%esp)
call_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_,4(%esp)
movl %eax, (%esp)
call_ZNSolsEPFRSoS_E
movl8(%ebp), %eax
movl %eax,4(%esp)
movl $_ZSt4cout, (%esp)
call_ZNSolsEi
movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_,4(%esp)
movl %eax, (%esp)
call_ZNSolsEPFRSoS_E
movl12(%ebp), %eax
movl %eax,4(%esp)
movl $_ZSt4cout, (%esp)
call_ZNSolsEf
movl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_,4(%esp)
movl %eax, (%esp)
call_ZNSolsEPFRSoS_E
leave
.cfi_restore5
.cfi_def_cfa4,4
ret
.cfi_endproc
.LFE968:
.size _Z4funcif, .-_Z4funcif
.globl main
.type main, @function
main:
.LFB969:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset8
.cfi_offset5, -8
movl %esp, %ebp
.cfi_def_cfa_register5
andl $-16, %esp
subl $16, %esp
call_Z4funcv
movl $5, (%esp)
call_Z4funci
movl $0x40a00000, %eax
movl %eax,4(%esp)
movl $5, (%esp)
call_Z4funcif
movl $0, %eax
leave
.cfi_restore5
.cfi_def_cfa4,4
ret
.cfi_endproc
可以看到,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 <sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
intopen(constchar*pathname,intflags);
intopen(constchar*pathname,intflags, mode_t mode);
第二个 open 函数的第三个参数用来表明创建文件的权限,所以这就是 C 实现函数重载活生生的例子 :-)
那么如何实现 C 的函数重载呢,比较通用的做法是利用 C 的可变参数va_args:
#include <stdarg.h>
voidva_start(va_list ap, last);
type va_arg(va_list ap, type);
voidva_end(va_list ap);
voidva_copy(va_list dest, va_list src);
以下是一个简单的例子,"重载"了两个函数,第一个函数是两个参数,第二个函数带了三个函数,其中第三个函数是可选的,
#include <stdio.h>
#include<stdarg.h>
voidva_overload2(intp1,intp2)
{
printf("va_overload2 %d %d\n", p1, p2);
}
voidva_overload3(intp1,intp2,intp3)