https://zhuanlan.zhihu.com/p/22437704
今天借着函数与函数指针和大家体会一下指针的灵活。
先看示例:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
void print_something(void)
{
printf("hello\n\r");
return;
}
int hello(int a)
{
printf("hello %d \n\r");
}
int main(void)
{
print_something();
hello(1);
return 0;
}
这一段代码编译出汇编,核心部分如下:
print_something:
0000000000400526: push %rbp
0000000000400527: mov %rsp,%rbp
000000000040052a: mov $0x400604,%edi
000000000040052f: mov $0x0,%eax
0000000000400534: callq 0x400400 <printf@plt>
0000000000400539: nop
000000000040053a: pop %rbp
000000000040053b: retq
hello:
000000000040053c: push %rbp
000000000040053d: mov %rsp,%rbp
0000000000400540: sub $0x10,%rsp
0000000000400544: mov %edi,-0x4(%rbp)
0000000000400547: mov -0x4(%rbp),%eax
000000000040054a: mov %eax,%esi
000000000040054c: mov $0x40060c,%edi
0000000000400551: mov $0x0,%eax
0000000000400556: callq 0x400400 <printf@plt>
000000000040055b: mov -0x4(%rbp),%eax
000000000040055e: leaveq
000000000040055f: retq
main:
0000000000400560: push %rbp
0000000000400561: mov %rsp,%rbp
0000000000400564: callq 0x400526 <print_something>
0000000000400569: mov $0x1,%edi
000000000040056e: callq 0x40053c <hello>
0000000000400573: mov $0x0,%eax
从以上可以看到,在main函数中
callq 0x400526 <print_something>
callq 0x40053c <hello>
用于调用函数print_something和hello ,其使用callq指令+函数首地址来定位函数。
我们看到hello和print_something函数在首地址都压入堆栈,在最后都调用retq返回。
因此,我们可以得到这个结论:
只要知道函数的首地址就可以定位这个函数了
本例中,我们只要知道首地址0x0000000000400526就定位了函数void print_something(void) ;知道首地址0x000000000040053c就定位了函数int hello(int a);
函数首地址的位数由编译器及运行平台决定,但是其大小一定等于同样编译器和运行平台的int位数。
!!! 更新:
有知友对函数指针的大小提出质疑,查阅资料之后发现我的认识确实是错误的。
C99 specA pointer to void shall have the same representation and alignment requirements as a pointer to a character type. Similarly, pointers to qualified or unqualified versions of compatible types shall have the same representation and alignment requirements. All pointers to structure types shall have the same representation and alignment requirements as each other. All pointers to union types shall have the same representation and alignment requirements as each other. Pointers to other types need not have the same representation or alignment requirements.section 6.3.2.3
A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer.第一段就是说,void*和所有指向各种数据的指针都和char *一致,包括大小,对齐方式。第二段就是说,所有指向函数的指针都一致。
因为函数地址就是bit数等于int的数据而已,所以有多种方法可以取得函数地址
1. 指向int 的指针(int *)
int * p_function = (int *)print_something;
其语句完整的写法应该为:
int * p_function = (int *) &print_something;
编译器处理时遇到函数的名字会自动取其地址,所以&取地址符可以省略。
2. int
int function_address = (int)print_something;
注意:这个编译器会有警告,可以运行,但是不推荐使用
3. void *(万能指针)
void * p = print_something;
print_something省略了取地址符,这个是获取一个函数的首地址,并把它赋值给万能指针。
4. 常量
0x0000000000400526就可以表示print_something函数
0x000000000040053c就可以表示hello函数
相应的,我王小军有一百种方式访问函数(大雾
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
/** @note 定义类型函数指针,方便指针转换 */
typedef void ( * void_function_type)(void); //print_something类型的函数
void print_something(void)
{
printf("hello\n\r");
return;
}
int main(void)
{
// int *
int * p_function = (int *)print_something;
((void_function_type)p_function)(); //int *强制转换成函数指针,进而访问函数
//int
int function_address = (int)print_something;
((void_function_type)function_address)(); //int变量存储了函数的首地址,强制转换成函数指针进行访问,注意这个方法有警告
//void *
void * p = print_something;
((void_function_type)p)(); //万能指针转化为函数指针,进行函数访问
//constant
((void_function_type)0x0000000000400526)(); //直接利用函数的首地址常量进行函数访问
}
从以上三部分内容我们可以知道,其实在C语言中,函数名就是表示函数的首地址,函数指针就是指向函数首地址的指针。
更新:其中利用常量进行函数访问时,在《C缺陷和指针》中有这样一个例子:
为了计算机启动时,硬件首先调用首地址位0位置的子例程,采用如下语句
(* (void (*) ()) 0 )();
函数指针用处很多,但是本文只介绍使用最多的用法,回调函数,详细见示例。
test.h
#ifndef TEST_H_
#define TEST_H_
#include <stdint.h>
typedef enum
{
TYPE_X,
TYPE_Y,
TYPE_Z
} type_t;
typedef uint32_t data_t;
//data_struct
typedef struct
{
type_t type;
data_t data;
}data_struct_t;
//callback_function type
typedef void (* callback_function_t)(data_struct_t *data_struct);
//init function
void test_init(callback_function_t callback_function);
//update_function
void test_update(void);
#endif /* TEST_H_ */
其中
typedef void (* callback_function_t)(data_struct_t *data_struct);
定义了我们需要的回调函数类型,外部传递的回调函数,要按照这个函数指针的格式定义
test.c
#include "test.h"
static callback_function_t _callback_function;
static uint32_t count_num = 0;
void test_init(callback_function_t callback_function)
{
_callback_function = callback_function;
}
void test_update(void)
{
count_num++;
if(count_num % 2 == 0)
{
data_struct_t my_data;
my_data.type = TYPE_X;
my_data.data = count_num;
_callback_function(&my_data);
}
if(count_num % 3 == 0)
{
data_struct_t my_data;
my_data.type = TYPE_Y;
my_data.data = count_num;
_callback_function(&my_data);
}
if(count_num % 5 == 0)
{
data_struct_t my_data;
my_data.type = TYPE_Z;
my_data.data = count_num;
_callback_function(&my_data);
}
}
在内部实现时,当检测到合适的情况通知外部回调函数
main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include "test.h"
void my_callback(data_struct_t *data_struct)
{
printf("type: %d \t data: %d \n\r", data_struct->type, data_struct->data);
}
int main(void)
{
test_init(my_callback);
for(int i = 0; i < 100; i++)
{
test_update();
}
}
在main.c中可以看到,我们按照格式定义了回调函数,并传给test相关文件,当test运行时,相应情况出现就会通知main中的回调函数。
知友@冒泡指教,感觉自己的认识还是有很多误区的。
查阅ISO/ANSI标准
ANSI 6.3.2.2 Pointers
1. A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
8. A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.
第1点指明了void *指针可以和不完全类型以及对象类型(非面向对象的对象)相互转换,第8点说明了函数指针之间可以相互转换,但是标准并没有定义void * 和函数指针之间如何转换。
为了避免这种未定义行为,建议采用如下方式操作:
void * p = &print_something; //获取函数地址
void_function_type p_function;
*(void**)&p_function = p;
p_function();
最近在看《C专家编程》有函数指针和void *直接转换的用法(P189),例子稍微变化一下如下:
extern int print_function(const char * ch);
void * f = (void *)print_function;
(*(int (*)(const char *))f)("hello\n\r");
关于函数指针和void *的转换总结如下:
这个如何使用我也比较迷茫了,可能按照第5点知友的使用比较合适了,如果有哪位大牛知道感谢告知了。
---
以上~ ^ ^
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。