关于函数指针的 强制转换 & void指针

  无情的搬砖机器= =

一、大佬链接 和 结论

  下面链接是这篇博文的主力大佬,我只是润润色。
函数指针的强制类型转换与void指针
关于函数指针类型强制转换的一些摸索

先说结论:

  • 函数指针的指针参数只是一个标记,总之其类型更多是为了编译器检查以及代码可读性,实际工作时只要产生强制类型转换之后,其类型就没有意义了,只是单纯的一个指针而已。
    • 换一种说法,函数指针中的形参最后使用的效果只由函数指针指向的实际函数的形参类型决定,中间处理过程只是作为一个没有类型的32位或者16位二进制数来处理。效果跟函数指针申明的形参没有多大关系。
  • 我们在使用函数指针时,需要保证调用该指针时的入参与该指针指向的真实函数的入参保持一致

二、 常见的情况

2.1 函数指针常见用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "stdio.h"

/* 真实函数fun_int */
void fun_int(int *p_fun)
{
printf("整数:%d\n", *p_fun);
}

int main()
{
int i = 200; /* 真实整数变量 */
int* p_i = &i; /* 整数指针 */

void (*f_ptr)(int *p_fun); /* 函数指针 */
f_ptr = fun_int; /* 函数指针赋值 */
f_ptr(p_i);
return 0;
}c

运行结果如下:

1
整数:200

  总之就是,定义一个包含返回值类型和参数的函数指针,然后把一个返回值类型,参数类型及个数都相同的函数名(即地址)赋值给这个函数指针,这个函数指针就可以当成普通函数用了。

2.2 函数指针强制类型转换

  此处只展示一种情况:函数指针和真实函数的返回值及参数个数都相同,只有参数类型不同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "stdio.h"

/* 真实函数fun_float */
void fun_float(float *p_fun)
{
printf("小数:%f\n", *p_fun);
}

int main()
{
int i = 20000; /* 真实整数变量 */
int* p_i = &i; /* 整数指针 */

float f = 9.999f; /* 真实浮点变量 */
float* p_f = &f; /* 浮点指针 */

void (*f_ptr)(int *p_fun); /* 函数指针 */
f_ptr = (void (*)(int *))fun_float; /* 函数指针强制类型转换 */
f_ptr(p_i); /* 传入int指针 */
f_ptr((int *)p_f); /* 传入float指针 */
return 0;
}

运行结果如下:

1
2
小数:0.000000
小数:9.999000

  此时我们可以发现一件有趣的事情:尽管函数指针f_ptr的参数是int,但传入的int指针打印数据不正常(正常为20000),而类型不匹配的float指针却是对的(9.999)。

对此我总结了两点:

  • 函数指针的指针参数只是一个标记(或者说只是一个保存指针的地址?),总之其类型更多是为了编译器检查以及代码可读性,实际工作时只要产生强制类型转换之后,其类型就没有意义了,只是单纯的一个指针而已。
  • 我们在使用函数指针时,需要保证调用该指针时的入参与该指针指向的真实函数的入参个数保持一致。

2.3 以void指针作为参数的函数指针

  结合之前的结论,在函数指针中包含指针参数时,都可以用void指针进行替代(如果类型很明确就另当别论,直接写)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "stdio.h"

/* 真实函数fun_int */
void fun_int(int *p_fun)
{
printf("整数:%d\n", *p_fun);
}

/* 真实函数fun_float */
void fun_float(float *p_fun)
{
printf("小数:%f\n", *p_fun);
}

int main()
{
int i = 20000; /* 真实整数变量 */
int* p_i = &i; /* 整数指针 */

float f = 9.999f; /* 真实浮点变量 */
float* p_f = &f; /* 浮点指针 */

void (*f_ptr)(void *p_fun); /* 函数指针 */
f_ptr = (void (*)(void *))fun_int; /* 强制为void* */
f_ptr(p_i);
f_ptr = (void(*)(void *))fun_float; /* 强制为void* */
f_ptr(p_f);

return 0;
}

运行结果如下:

1
2
整数:20000
小数:9.999000

  如此一来,只需要在函数指针赋值时进行强制类型转换就可以适应不同的真实函数。在调用时只要保证以void指针作为参数的函数指针结论中的第2点(形参个数保持一致)即可。

三、参数个数不同的函数指针

  这一块的知识简单了解一下就行。太内核了,从外面看就是玄学。

3.1 “运气”例子

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>

typedef void (*F1)(int, int);
typedef void (*F2)(int);

void ff1(int a, int b)
{
printf("ff1\n");
printf("%d,%d\n", a, b);
}

void ff2(int a)
{
printf("ff2\n");
printf("%d\n", a);
}

int main()
{
F1 f1 = (F1)ff2;
f1(1,2);

F2 f2 = (F2)ff1;
f2(3);

return 0;
}

= =本人运行结果如下:

1
2
3
4
ff2
1
ff1
3,2147483646

原本例子的运行结果如下:

1
2
3
4
ff2
1
ff1
3,2

  从运行结果中可以看到,当函数ff2被强制转换成一个F1类型的函数后,ff2的形参a的取值为F1类型函数的第一个形参的值,这里说明程序在执行的时候,如果该函数为一个只有n个形参的函数,如果你传递给他m个形参(n<=m),则该函数只会使用前n个形参。至于这里为什么可以允许这样的操作,后面将试探的分析一下。

  接着,我们试图把一个ff1类型的函数,强制转换为一个F2类型的函数,并执行,执行结果还是在上面的图中,可以看到函数ff1的运行结果为3,2。

  问题来了,我明明只传递给ff1这个函数1个参数,但为什么编译器没报错?虽然类型经过了强制转换,也许可以骗过编译器,但是在执行的时候,明明少了一个参数,程序执行的时候为什么也不报错?而且第二个参数的值还比较诡异,从打印的情况看,第二个参数为2,该值正是调用ff2的时候传递的第二个参数,该参数没被ff2处理,这里却被ff1处理,神马情况?

3.2 测试”运气”

  首先,有一点,当每个函数被调用时,都被赋予了相对独立的栈空间,当该函数的生命周期结束后,该栈空间同时也被回收。

  这样,当我们的main函数执行后,main函数有一个栈空间,假设为mem_A。那么根据上述实验结果,我这里有一个猜想,关于函数的参数是如何实现传递的。我的猜想是这样的,main函数在mem_A的栈空间中单独保留了一块类存给参数传递使用,假设该类存空间为mem_B。当一个函数运行时,程序把需要传递的参数按照顺序,放置在mem_B中,且每次都是从头开始放置。

  回到刚刚的实验中,当ff2被执行后,mem_B中应该存放着0x00000001和0x00000002,这时再执行ff1,由于这里把ff1强制转换成了只含有一个形参的函数,所以当我们只传递给ff1一个参数3时,mem_B中应该存放着0x00000003和0x00000002,这样,ff1调用的结果可以符合以上输出结果,这里为了确定这样的结果不是巧合,又进行了数次实验,均与以上猜想相符。按照以上猜想,如果ff1先执行,ff2后执行,则ff1的第二个参数应该为一个随机数,为了验证该想法,又进行了如下实验。

其他代码都一样,就是把两个函数的执行顺序掉了一下顺序,输出结果下:

1
2
3
4
5
6
7
8
9
10
int main()
{
F2 f2 = (F2)ff1;
f2(3);

F1 f1 = (F1)ff2;
f1(1,2);

return 0;
}

本人和原例运行类似,结果如下:

1
2
3
4
ff1
3,-417232744
ff2
1

  也就是说,现在的编译器则是另外开辟栈空间。由于是另外开辟的情况,无法验证第二位博主的: 参数的存储是以4字节为一个存储单元的,也就是说当一个char类型的变量作为参数传递,在mem_B中占的内存大写依旧为4字节。

3.3 (无卵用)总结

  由于不能复现之前博主的情况,也无法确定他的结论。但是也有发现:

1
2
3
4
5
6
7
8
9
10
int main()
{
F1 f1 = (F1)ff2;
f1(1,2);

F2 f2 = (F2)ff1;
f2(3);

return 0;
}

本人运行结果如下:

1
2
3
4
ff2
1
ff1
3,2147483646

  无论如何改动f1中的第二个参数,最后打印的随机值都不会发生变化。但是如果更换成ff2先执行,ff1后执行,该值就会随着f1中的第二个参数变化而变化。

简单分析,按照程序执行顺序:

  • 如果先执行:多形参的函数;会多开辟多的栈空间,但是由于被强转为单形参,另外一个形参值则为一个随机值。后面执行单形参函数,开辟的栈小,多出来的形参会影响到之前双形参的栈空间存放值。
  • 如果先执行:单形参函数;实际的栈开辟小,打印出来的随机值则不受第二个形参输入的影响(不是明确关系,值波动很大)。
-------------本文结束感谢您的阅读-------------