标签:理解 代码 指针的指针 无法 def pre 只读 显示 引用
void (*funptr)(int param);
这就是一个简单的函数指针的声明。顾名思义,函数指针是一个特殊的指针,它用于指向函数被加载到的内存首地址,可用于实现函数调用。
函数名也是指向函数的内存首地址的,他和函数指针有什么不同?——既然他是指针,而且不是const的,那么他就是灵活可变的,通过赋值不同的函数来实现不同的函数调用。
然而他也有自己的限制(函数签名——返回值类型和参数类型),那不是和覆盖、多态实现的功能一样了么?额。。。要这么理解也行,但不全对。
上面说到函数指针的功能类似覆盖或多态,覆盖和多态更多体现的是对象自身的特征和对象之间的继承关联,而函数指针则没这么多讲究,他就是灵活。
函数指针不需要依附于对象存在,他可以用来解决基于条件的多个函数筛选,也可以处理完全无关的几个函数。
所以他的作用,什么封装性好、用于回调函数、实现多态等等,随便了,只有一条,符合他的函数签名并且可达。
和变量指针类似,声明->赋值->使用 或者 定义->使用
int add(int a,int b) { return a + b; } //1.声明->赋值->使用 int (*fun)(int a,int b); fun = add; //也可使用 fun = &add;从某篇文章看到是历史原因,后面稍作分析 fun(5,10); //也可使用 (*fun)(5,10);原因同上
//2.通过宏定义函数指针类型 #define int (*FUN)(int,int); FUN fun = NULL; fun = add; fun(5,10); //3.定义->使用 int (*fun)(int a,int b) = add; fun(5,10); //4.宏定义 #define int (*FUN)(int,int); FUN fun = add; fun(5,10);
上面的代码中,又是取地址符&,又是取引用符*,结果还能相互赋值,交叉调用,这又怎么理解?
首先来看下函数指针、函数名的类型。对于函数指针fun,类型为 int (*)(int,int),这个很好理解,函数名add的类型,通过VS在静态情况下用鼠标查看,类型为int (*)(int,int)。
what?为啥不是int ()(int,int)呢?这个我们可以类比数组。数组名为指向内存中数组首地址的指针,同时数组名可当做指针对数组进行操作。而对于函数名,通过在VS下查看汇编代码可以知道,编译器将函数名赋值为函数加载入内存的首地址,通过call 函数名来跳转到相应内存地址进行函数的执行。所以对这里的函数名为指针类型也可以理解。
这样 fun = add 我们可以理解了,那 fun = &add 又是什么鬼? (*fun)(5,10)也可以理解,fun(5,10)呢?下面来看看汇编代码
//原始代码 int main() { FUN f = NULL; f = &add; FUN f1 = NULL; f1 = add; add(1,2); std::cout<< add <<" "<< &add <<" " << *add <<std::endl; std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; std::cout<< (*f)(5, 10) << " " << f(5,10) <<std::endl; std::cout << (*add)(5, 10) << " " <<(&add)(5, 10) << std::endl; } //对应汇编代码 //对add函数作了一层跳转(从标识add->add函数),记录了add函数的入口地址 add: 000412F8 jmp add (042180h) //add函数汇编代码 int add(int a, int b) { 00042180 push ebp 00042181 mov ebp,esp 00042183 sub esp,0C0h 00042189 push ebx 0004218A push esi 0004218B push edi 0004218C lea edi,[ebp-0C0h] 00042192 mov ecx,30h 00042197 mov eax,0CCCCCCCCh 0004219C rep stos dword ptr es:[edi] return a + b; 0004219E mov eax,dword ptr [a] 000421A1 add eax,dword ptr [b] } int main() { 00042450 push ebp 00042451 mov ebp,esp 00042453 sub esp,0DCh 00042459 push ebx 0004245A push esi 0004245B push edi 0004245C lea edi,[ebp-0DCh] 00042462 mov ecx,37h 00042467 mov eax,0CCCCCCCCh 0004246C rep stos dword ptr es:[edi] //可以看到,对编译器来说&add和add其实是一样的,都对应内存中的标识add,即上面的jmp代码的地址 //这里f、f1都被当作指针,指向标识add,与 int(*)(int,int) 中的那个*对应 //因为在同一代码段,通过offset获取段内偏移即可实现跳转 FUN f = NULL; 0004246E mov dword ptr [f],0 f = &add; 00042475 mov dword ptr [f],offset add (0412F8h) FUN f1 = NULL; 0004247C mov dword ptr [f1],0 f1 = add; 00042483 mov dword ptr [f1],offset add (0412F8h) //通过call调用函数,函数对应标识add add(1,2); 0004248A push 2 0004248C push 1 0004248E call add (0412F8h) 00042493 add esp,8 //这里可以看到,对于add、&add、*add 的值,编译器都当做标识add,由此可以猜测,对于函数名编译器有特殊处理 //其实这个也可以理解,首先函数名即函数的内存首地址,对这个地址值取地址没有什么意义,函数调度由系统完成,而指针的指针作用就是改变第一层指针的值,改变函数名的指向没有意义 //而对其取值,那就是代码的机器码,因为代码存放于只读段,我们不会也不能对其进行重写、拷贝等操作,机器码我们也无法操作,所以这个也没有意义 std::cout<< add <<" "<< &add <<" " << *add <<std::endl; 00042496 mov esi,esp //cout为从右往左,全部压栈后再先进后出地输出,第一个为std::endl 00042498 push offset std::endl<char,std::char_traits<char> > (041410h) 0004249D mov edi,esp 0004249F push offset add (0412F8h) 000424A4 push offset string " " (049B30h) 000424A9 mov ebx,esp 000424AB push offset add (0412F8h) 000424B0 push offset string " " (049B34h) 000424B5 mov eax,esp 000424B7 push offset add (0412F8h) 000424BC mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 000424C2 mov dword ptr [ebp-0DCh],eax 000424C8 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 000424CE mov ecx,dword ptr [ebp-0DCh] //函数调用结束后的平衡堆栈检测 000424D4 cmp ecx,esp 000424D6 call __RTC_CheckEsp (04115Eh) 000424DB push eax 000424DC call std::operator<<<std::char_traits<char> > (041438h) 000424E1 add esp,8 000424E4 mov ecx,eax 000424E6 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 000424EC cmp ebx,esp 000424EE call __RTC_CheckEsp (04115Eh) 000424F3 push eax 000424F4 call std::operator<<<std::char_traits<char> > (041438h) 000424F9 add esp,8 000424FC mov ecx,eax 000424FE call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 00042504 cmp edi,esp 00042506 call __RTC_CheckEsp (04115Eh) 0004250B mov ecx,eax 0004250D call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 00042513 cmp esi,esp 00042515 call __RTC_CheckEsp (04115Eh) //函数指针f、指针取值*f 都是 dword ptr [f],即标识指针f指向的内存的值(标识add的偏移),如果是取值,*f与*add一样没有意义 //而&f即标识指针f的值,这里取地址的意义即对应他的指针类型,可以指向不同内存地址来调用不同函数 //由此可以猜测,对于函数指针编译器也有类似的特殊处理 std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; 0004251A mov esi,esp 0004251C push offset std::endl<char,std::char_traits<char> > (041410h) 00042521 push offset string " " (049B30h) 00042526 mov edi,esp 00042528 mov eax,dword ptr [f] 0004252B push eax std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; 0004252C push offset string " " (049B30h) 00042531 mov ebx,esp 00042533 lea ecx,[f] 00042536 push ecx 00042537 push offset string " " (049B30h) 0004253C mov eax,esp 0004253E mov edx,dword ptr [f] 00042541 push edx 00042542 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 00042548 mov dword ptr [ebp-0DCh],eax 0004254E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 00042554 mov ecx,dword ptr [ebp-0DCh] 0004255A cmp ecx,esp 0004255C call __RTC_CheckEsp (04115Eh) 00042561 push eax 00042562 call std::operator<<<std::char_traits<char> > (041438h) 00042567 add esp,8 0004256A mov ecx,eax 0004256C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 00042572 cmp ebx,esp 00042574 call __RTC_CheckEsp (04115Eh) 00042579 push eax 0004257A call std::operator<<<std::char_traits<char> > (041438h) 0004257F add esp,8 00042582 mov ecx,eax 00042584 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A4h)] 0004258A cmp edi,esp 0004258C call __RTC_CheckEsp (04115Eh) 00042591 push eax 00042592 call std::operator<<<std::char_traits<char> > (041438h) 00042597 add esp,8 std::cout<< f <<" " << &f <<" " << *f <<" "<<std::endl; 0004259A mov ecx,eax 0004259C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 000425A2 cmp esi,esp 000425A4 call __RTC_CheckEsp (04115Eh) //函数调用时 f、*f 都被当成 call dword ptr [f],同上,而&f对应的是指针,就不能进行函数调用了 std::cout<< (*f)(5, 10) << " " << f(5,10) <<std::endl; 000425A9 mov esi,esp 000425AB push offset std::endl<char,std::char_traits<char> > (041410h) 000425B0 mov edi,esp 000425B2 push 0Ah 000425B4 push 5 000425B6 call dword ptr [f] 000425B9 add esp,8 000425BC cmp edi,esp 000425BE call __RTC_CheckEsp (04115Eh) 000425C3 mov edi,esp 000425C5 push eax 000425C6 push offset string " " (049B30h) 000425CB mov ebx,esp 000425CD push 0Ah 000425CF push 5 000425D1 call dword ptr [f] 000425D4 add esp,8 000425D7 cmp ebx,esp 000425D9 call __RTC_CheckEsp (04115Eh) 000425DE mov ebx,esp 000425E0 push eax 000425E1 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 000425E7 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 000425ED cmp ebx,esp 000425EF call __RTC_CheckEsp (04115Eh) 000425F4 push eax 000425F5 call std::operator<<<std::char_traits<char> > (041438h) 000425FA add esp,8 000425FD mov ecx,eax 000425FF call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 00042605 cmp edi,esp 00042607 call __RTC_CheckEsp (04115Eh) 0004260C mov ecx,eax 0004260E call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 00042614 cmp esi,esp 00042616 call __RTC_CheckEsp (04115Eh) //如上所说,*add、&add、*add三者相同,所以下面两个调用也成立,但是为了良好的编码风格,尽量避免 std::cout << (*add)(5, 10) << " " <<(&add)(5, 10) << std::endl; 0004261B mov esi,esp 0004261D push offset std::endl<char,std::char_traits<char> > (041410h) 00042622 push 0Ah 00042624 push 5 00042626 call add (0412F8h) 0004262B add esp,8 0004262E mov edi,esp 00042630 push eax 00042631 push offset string " " (049B30h) 00042636 push 0Ah 00042638 push 5 0004263A call add (0412F8h) 0004263F add esp,8 00042642 mov ebx,esp 00042644 push eax 00042645 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (04D098h)] 0004264B call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 00042651 cmp ebx,esp 00042653 call __RTC_CheckEsp (04115Eh) 00042658 push eax 00042659 call std::operator<<<std::char_traits<char> > (041438h) 0004265E add esp,8 00042661 mov ecx,eax 00042663 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0A8h)] 00042669 cmp edi,esp 0004266B call __RTC_CheckEsp (04115Eh) 00042670 mov ecx,eax 00042672 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (04D0ACh)] 00042678 cmp esi,esp 0004267A call __RTC_CheckEsp (04115Eh) }
综上所述,对于函数名add,add、*add、&add三者等价,都指向函数首地址;对于函数指针f,f 和 *f 等价,同样是指向函数首地址;而&f 为函数指针的地址,因为函数指针 f 的值可变,可指向不同的函数。
当我们调试代码时,将指针移到函数名或函数指针上,会显示其类型为 functionprt,编译器对其的特殊处理可能就源自于此。
网上有些文章对这种机制的解释为,出于历史的原因(从面向过程到面向对象的过渡,函数指针与对象指针的关系等等)
标签:理解 代码 指针的指针 无法 def pre 只读 显示 引用
原文地址:http://www.cnblogs.com/hellscream-yi/p/7943848.html