C实现“动态绑定”
2010-7-26
烛秋
在论坛上看到这样一个问题:“用C能实现C++的动态绑定吗?”网址:http://topic.csdn.net/u/20100624/21/3d7eda37-cbf7-4e36-a549-f2d6f1a3eeed.html?47092。。现在看当时我的回复(ID:wuxupeng999),觉得理解上还是有些不足,这里再总结一下。
有网友给出了利用结构体和函数指针实现的方法。看了之后我觉得有点别扭,于是我自己再对动态绑定作了认真的理解,并做下总结。
一、什么是动态绑定
动态绑定就是某个对象要到程序执行时才知道是哪个对象。也就是说,最终编译成汇编指令的时候,可能是这样子的:"call [eax]",指令调用哪个函数,只有到程序执行的时候才会知道。在程序执行的时候,通过修改eax的值,使得程序调用不同的函数,这就是所谓的动态绑定。从汇编看来这是很平常的,例如,编写了一个函数,这个函数的入口参数eax是另一个函数地址,然后程序call [eax],这就动态了。当然也可以不用寄存器而用别的方式,例如call [地址a],地址a里边存放的值是某个函数的地址,修改地址a里的值就行了。
我觉得对于汇编来说,动态就是修改call后边的寄存器/地址的值,对于C来说就是修改指针所指向的函数地址,而指针也就是保存函数地址的变量,还是跟汇编一样。C++的动态绑定是高级语言的说法了,C++的动态绑定可以说是基类指针指向子类对象,然后通过基类指针调用子类实现的函数。而对于C和汇编来说,也就是把某个保存地址的变量的值修改一下。从这一点来说,用C实现动态绑定,没有必要搞什么模拟虚函数、虚函数表,直接来个(void*)指针,然后让它指向不同的函数,不过在函数调用的时候C编译器必须知道函数原型,可以使用typedef。
二、测试
代码①
///////////////////////////////////////////////// #include <stdio.h> #include <windows.h> int test_add(int a, int b); int test_sub(int a, int b); void all(void *p); int main () { /*定义一个void类型指针*/ void *fp; /*指向test_add*/ fp = (int*)test_add; printf("test test_add:\n"); /*此时all执行test_add,输出7*/ all(fp); /*指向test_sub*/ fp = (int*)test_sub; printf("test test_sub:\n"); /*此时all执行test_sub,输出-1*/ all(fp); system("pause"); return 0; } //////////////////////////// /*函数1*/ int test_add(int a, int b) { return a+b; } //////////////////////////// /*函数2*/ int test_sub(int a, int b) { return a-b; } //////////////////////////// /*依靠输入参数的不同,调用不同的函数。动态绑定*/ void all(void *p) { int a = 3; int b = 4; typedef int (*fp)(int a, int b); int c = ((fp)p)(3,4); printf("%d\n",c); } /*输出: test test_add: 7 test test_sub: -1 Press any key to continue . . . */
三、比较分析
下面的代码是某网友用结构体和函数指针实现的:
代码②
///////////////////////////////////////////////// #include <stdio.h> ///////////////////////////////////////////////// typedef struct base { void (*vfun)(void); }Base; typedef struct derived { Base b; void (*vfun)(void); } Derived; ///////////////////////////////////////////////// void Bfun() { puts("Base vfcn"); } void Dfun() { puts("Derived vfcn"); } ///////////////////////////////////////////////// void Baseconstruct(Base* b) { b->vfun=Bfun; } void Derivedconstruct(Derived* d) { d->b.vfun=Dfun; d->vfun=Dfun; } ///////////////////////////////////////////////// int main(void) { Base ba,*pb; Derived de; Baseconstruct(&ba); Derivedconstruct(&de); ba.vfun();//正常调用 de.vfun(); pb = &ba;//动态调用 pb->vfun(); pb = (Base*)&de; pb->vfun(); getchar(); return 0; } /* Base vfcn Derived vfcn Base vfcn Derived vfcn */
代码②用结构体模拟类,然后还搞出了构造函数,模拟了很多C++的外表,但是代码②没有办法采用真正的虚函数表,所以在结构体里边保存了两个同样的指针。
这就是为什么需要:d->b.vfun=Dfun。“父类”(结构体)指针在调用vfun函数的时候,调用的结果就是这条指令的功劳。这里如果把Dfun改成其它的,那“父类”指针调用输出就是其它的了。这个“动态绑定”还是通过“子类”(结构体)对象来调用函数的。
动态绑定其实没必要搞得那么复杂,动态绑定只是C++的一个功能而已,模仿了那么多,把一些不必要的东西也加上去了,反而混淆了动态绑定的根本。