c语言:基于函数指针的两个示例分析

第一个:
------------------------------------------------------


代码如下:

#include <stdio.h>
#include <string.h>
void tell_me(int f(const char *, const char *));
int main(void)
{
   tell_me(strcmp);
   tell_me(main);
   return 0;
}
void tell_me(int f(const char *, const char *))
{
   if (f == strcmp) /* <-----我不理解这里*/
      printf("Address of strcmp(): %p\n", f);
   else
      printf("Function address: %p\n", f);
}

--------------------------------------------------------------
其中我不理解的是,这个程序表达的应该是说f是一个指向函数的指针,判断的时候是判断f是否指向函数strcmp,如果是的话,就输出strcmp这个函数的地址.如果不是,就输出main函数的地址
因为函数名可以作为指针,所以if (f == strcmp)应该是说判断2个指针的地址是否相同对吧?
我用gdb 断点到此,print f和printfstrcmp得到的是不同的地址啊,并且可以发现f和*f的内容居然一样,strcmp和*strcmp也一样,请问是什么原因,如何解释?

(gdb) print f
$1 = (int (*)(const char *, const char *)) 0x8048310 <strcmp@plt>
(gdb) print strcmp
$2 = {<text variable, no debug info>} 0xb7e59d20 <strcmp>
(gdb) n
16 printf("Address of strcmp(): %p\n", f);
(gdb) print strcmp
$3 = {<text variable, no debug info>} 0xb7e59d20 <strcmp>
(gdb) print *strcmp
$4 = {<text variable, no debug info>} 0xb7e59d20 <strcmp>
(gdb) print *f
$5 = {int (const char *, const char *)} 0x8048310 <strcmp@plt>
(gdb) n
Address of strcmp(): 0x8048310
19 }
(gdb) n
后来我查到plt是指的过程链接表,是不是说只有在执行到f == strcmp时候,才把f的地址和strcmp的位置指向同一位置?

后来别人通过反汇编发现的情况:
==============================================
如下红色的几行,main与strcmp此时为常量(你也会发现没有.data段),在汇编代码中他是把这两个常量写入函数堆栈,然后调用函数,作出对比,然后输出。而你所说的 f ,也就是函数参数,实际上它只作为预分配的参考(汇编代码中,你是找不到 f 的)。
-------------------------------------------------------------------------------------
.file "1.c"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $4, %esp
movl $strcmp, (%esp)
call tell_me
movl $main, %eax
movl %eax, (%esp)
call tell_me
movl $0, %eax
addl $4, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.section .rodata
.LC0:
.string "Address of strcmp(): %p\n"
.LC1:
.string "Function address: %p\n"
.text
.globl tell_me
.type tell_me, @function
tell_me:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
cmpl $strcmp, 8(%ebp)
jne .L4
movl 8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
jmp .L6
.L4:
movl 8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC1, (%esp)
call printf
.L6:
leave
ret
.size tell_me, .-tell_me
.ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
.section .note.GNU-stack,"",@progbits
==================================================
00401090 push ebp //第一题的反汇编
00401091 mov ebp,esp
00401093 sub esp,40h
00401096 push ebx
00401097 push esi
00401098 push edi
00401099 lea edi,[ebp-40h]
0040109C mov ecx,10h
004010A1 mov eax,0CCCCCCCCh //应该说在函数传递时,f与strcmp的地址都相同
004010A6 rep stos dword ptr [edi]
13: printf("%0x\t%0x\n",f,strcmp); //看这里,输出f与strcmp的地址是相同的
004010A8 push offset strcmp (004011a0)
004010AD mov eax,dword ptr [ebp+8]
004010B0 push eax
004010B1 push offset string "%0x\t%0x\n" (0042201c)
004010B6 call printf (00401120)
004010BB add esp,0Ch
14: if (f == strcmp) /* <-----我不理解这里*/ //比较后,输出的地址同样是一样的,
004010BE cmp dword ptr [ebp+8],offset strcmp (004011a0)
004010C5 jne tell_me+4Ah (004010da)
15: printf("Address of strcmp(): %0x\n", f);
004010C7 mov ecx,dword ptr [ebp+8]
004010CA push ecx
004010CB push offset string "Address of strcmp(): %0x\n" (00422044)
004010D0 call printf (00401120)
004010D5 add esp,8
16: else
004010D8 jmp tell_me+5Bh (004010eb)
17: printf("Function address: %p\n", f);
004010DA mov edx,dword ptr [ebp+8]
004010DD push edx
004010DE push offset string "Function address: %p\n" (00422028)
004010E3 call printf (00401120)
004010E8 add esp,8=======================================================

第二个:
--------------------------------------------------------------------------------------------


代码如下:

#include <stdio.h>
#include <string.h>
int main(void)
{
   char p1[20] = "abc", *p2 = "pacific sea";
   printf("%s %s %s\n", p1, p2, strcat(p1, p2)); /*<-----问题出在这里*/
   return 0;
}

---------------------------------------------------------------------------------------------
输出我的认为应该是先输出p1, p2以后再执行strcat. 但是实际输出的情况:
abcpacific sea pacific sea abcpacific sea
可以发现strcat先于p1执行,改变了p1的内容,请问这个内部是怎么的顺序?难道printf不是顺序执行的?
=======================================================

得到的解答:
不同的编译器printf的函数参数进栈顺序不同,printf函数中的strcat可以看反汇编代码.
6: printf("%s\t%s\t%s\n", p1, p2, strcat(p1, p2)); /*<-----问题出在这里*/
00401045 mov edx,dword ptr [ebp-18h]
00401048 push edx
00401049 lea eax,[ebp-14h]
0040104C push eax
0040104D call strcat (00401130) //可以看到是先调用strcat函数的,
00401052 add esp,8
00401055 push eax
00401056 mov ecx,dword ptr [ebp-18h]
00401059 push ecx
0040105A lea edx,[ebp-14h]
0040105D push edx
0040105E push offset string "%s\t%s\t%s\n" (0042201c)
00401063 call printf (004010a0) //最后调用printf函数输出
00401068 add esp,10h
===============================================
汇编直观而简单的说明了一些疑问....
再说下gcc如何产生汇编代码:
1: gcc -S main.c 可以生成
2: gcc -c main.c 生成main.o
objdump -S -D main.o > main.asm
我更倾向于第二种.

(0)

相关推荐

  • 深入解析C语言中函数指针的定义与使用

    1.函数指针的定义     函数是由执行语句组成的指令序列或者代码,这些代码的有序集合根据其大小被分配到一定的内存空间中,这一片内存空间的起始地址就成为函数的地址,不同的函数有不同的函数地址,编译器通过函数名来索引函数的入口地址,为了方便操作类型属性相同的函数,c/c++引入了函数指针,函数指针就是指向代码入口地址的指针,是指向函数的指针变量. 因而"函数指针"本身首先应该是指针变量,只不过该指针变量指向函数.这正如用指针变量可指向整形变量.字符型.数组一样,这里是指向函数.C在编译时

  • C语言中的函数指针基础学习教程

    顾名思义,函数指针就是函数的指针.它是一个指针,指向一个函数.看例子: A) char * (*fun1)(char * p1,char * p2); B) char * *fun2(char * p1,char * p2); C) char * fun3(char * p1,char * p2); 看看上面三个表达式分别是什么意思? C)这很容易,fun3是函数名,p1,p2是参数,其类型为char *型,函数的返回值为char *类型. B) 也很简单,与C)表达式相比,唯一不同的就是函数的

  • 详解C语言结构体中的函数指针

    结构体是由一系列具有相同类型或不同类型的数据构成的数据集合.所以,标准C中的结构体是不允许包含成员函数的,当然C++中的结构体对此进行了扩展.那么,我们在C语言的结构体中,只能通过定义函数指针的方式,用函数指针指向相应函数,以此达到调用函数的目的. 函数指针 函数类型 (*指针变量名)(形参列表):第一个括号一定不能少. "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少.  注意指针函数与函数指针表示

  • 深入学习C语言中的函数指针和左右法则

    通常的函数调用     一个通常的函数调用的例子: //自行包含头文件 void MyFun(int x); //此处的申明也可写成:void MyFun( int ); int main(int argc, char* argv[]) { MyFun(10); //这里是调用MyFun(10);函数 return 0; } void MyFun(int x) //这里定义一个MyFun函数 { printf("%d\n",x); } 这个MyFun函数是一个无返回值的函数,它并不完成

  • 详解C语言编程中的函数指针以及函数回调

    函数指针: 就是存储函数地址的指针,就是指向函数的指针,就是指针存储的值是函数地址,我们可以通过指针可以调用函数. 我们先来定义一个简单的函数: //定义这样一个函数 void easyFunc() { printf("I'm a easy Function\n"); } //声明一个函数 void easyFunc(); //调用函数 easyFunc(); //定义这样一个函数 void easyFunc() { printf("I'm a easy Function\n

  • C++中函数指针详解及代码分享

    函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,如同数组的名字就是数组的起始地址. 1.函数指针的定义方式:data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn); c语言函数指针的定义形式:返回类型 (*函数指针名称)(参数类型,参数类型,参数类型,-); c++函数指针的定义形式:返回类型 (类名

  • c语言:基于函数指针的两个示例分析

    第一个:------------------------------------------------------ 复制代码 代码如下: #include <stdio.h>#include <string.h>void tell_me(int f(const char *, const char *));int main(void){   tell_me(strcmp);   tell_me(main);   return 0;}void tell_me(int f(const

  • C语言运用函数指针数组实现计算器功能

    本文实例为大家分享了C语言运用函数指针数组制作计算器的具体代码,供大家参考,具体内容如下 先来回顾一下概念: 指针数组 -- 存放指针的数组 函数指针 -- 存放函数地址的指针 函数指针数组 -- 存放函数指针的数组 接下来说说这次要制作的计算器的功能: 1.add -- 加法 2.sub -- 减法 3.mul -- 乘法 4.div -- 除法 0.exit -- 退出 具体来通过代码讲解: (1)首先写一个菜单程序,在运行程序时首先打印一次菜单. void menu() { printf(

  • C语言用函数指针实现一个特别的计算器

    目录 函数指针 实现计算器程序的思想 用函数指针调用加减乘除函数 完整代码与运行截图 函数指针 函数指针就是存放函数地址的指针. 函数指针的重要内容: 1.函数指针的使用:函数的类型(指针)(函数的形参类型)= 函数名:注意:函数的形参那里是不需要形参的变量名的.,如果函数没有形参,就不需要填任何东西,void也不行.这么使用完之后,这个指针就是一个函数指针了. 2.函数指针调用函数的方法:****(指针)(实参);注意:这里的指针有两种表达方式,比如定义了一个叫*p 的函数指针,函数的返回类型

  • C语言中函数指针的三种使用方法总结

     C语言中函数指针的三种使用方法总结 在这里分享一下自己的心得,希望和大家一起分享技术,如果有什么不足,还请大家指正.写出这篇目的,就是希望大家一起成长,我也相信技术之间没有高低,只有互补,只有分享,才能使彼此更加成长. 定义方式:int (*p)(int x, int y); 实现代码: #include <stdio.h> int sum(int x, int y){ return x + y; } int reduce(int x, int y){ return x - y; } int

  • 非常实用的MySQL函数全面总结详解示例分析教程

    目录 1.MySQL中关于函数的说明 2.单行函数分类 3.字符函数 4.数学函数 5.日期时间函数 6.其它常用系统函数 7.流程控制函数 8.聚合函数 1)聚合函数的功能和分类: 2)聚合函数的简单使用 3)五个聚合函数中传入的参数,所支持的数据类型有哪些? 4)聚合函数和group by的使用"最重要": 1.MySQL中关于函数的说明 "概念":类似java.python中的方法,将一组逻辑语句封装在方法体中,对外暴露方法名: "好处":

  • C语言编程函数指针入门精讲教程

    目录 一.指针引子 二.使用步骤 1.取函数地址 2.创建函数指针 3.通过函数指针调用函数的两种方法 三.函数指针进阶 总结 一.指针引子 示例:我们常常接触的指针大多有如下几类: 整形指针-存放整形地址,指向整形 字符指针-存放字符地址,指向字符 数组指针-存放数组地址(注意不是数组首元素地址),指向数组 由以上三个例子,我们能总结指针的共同点:存放某个类型变量的地址,指向那个类型的变量,但是在讲函数指针首先有一个问题:函数也有地址吗?我们用一段简单的代码来验证一下即可. #include<

  • C语言中函数指针与软件设计经验总结

    函数指针与软件设计 记得刚开始工作时,一位高手告诉我,说,longjmp和setjmp玩得不熟,就不要自称为C语言高手.当时我半信半疑,为了让自己向高手方向迈进,还是花了一点时间去学习longjmp和setjmp的用法.后来明白那不单是跳来跳去那样简单,而是一种高级的异常处理机制,在某些情况下确实很有用. 为了显示自己的技巧,也在自己的程序中用过几次.渐渐发现这样的技巧带来的好处是有代价的,破坏了程序的结构化设计,程序变得很难读,尤其对新手来说.终于明白这种技巧不过是一种调味料,在少数情况使用几

  • C++ 函数指针详细总结

    目录 1.函数指针 1.1 获取函数地址 1.2 声明函数指针 1.3 函数指针传参 1.4 调用函数 2.函数指针进阶 1.函数指针 函数指针顾名思义,就是指向函数的指针. 和数据类似,C++当中函数也有地址,函数的地址是存储函数机器语言代码的内存地址.我们可以将另外一个函数的地址作为参数传入函数,从而实现函数的灵活调用. 1.1 获取函数地址 获取函数地址的方法非常简单,只要使用函数名(后面不跟参数和括号)即可.比如我们有一个函数叫做think,那么think()是调用函数拿到结果,而thi

随机推荐