冷雨之家

Dying is the day worth living for

C 语言函数指针小测试

| Comments

首先看一道趣味题,来源:https://www.v2ex.com/t/492705

1
2
3
4
5
6
7
#include <stdio.h>

int main() {
[]
printf("%p\n", **********************p);
return 0;
}

在 [] 标记内加入一条语句,使得:

  1. 程序编译通过,无错误(警告随意);链接通过,无错误(警告随意)
  2. 程序能够运行,无崩溃和运行时错误
  3. 源代码为.c 文件
  4. 如果用 vs,则使用 Release 配置;用 gcc 也行
  5. 加入的语句不能忽略分号
  6. printf 一句必须能够正常输出内容

答案如下表

int(*p)(); 10个字符 兼容 gcc、g++、vs
int p(){} 9个字符 兼容 gcc,不兼容 g++、vs
p();}p(){ 9个字符 兼容 gcc、g++、vs

本文主要针对第一种答案来说明一下为什么函数指针可以被无线次解引用.

& *

说到指针,首先就一定要介绍两个操作符.

& 地址操作符(address operators) 单目前缀操作符,操作数为跟在后面的表达式. eg:&a,表示取操作数a的地址,也可以理解为取对象a的地址,或者取变量a的地址等等.

1
2
3
4
5
6
// 在64处理器的系统模式下,地址的长度为8个字节
int a;
printf("sizeof(&a)=%d\n",sizeof(&a));

**********result**********
sizeof(&a)=8

* 间接操作符 (indirection operators) 单目前缀操作数,同样也是跟在后面的表达式为操作数,可以使用间接操作符通过指针对象间接地访问它所指向对象的值. eg: *a,表示取指针对象a中存的地址所指向的值. (这里的表达非常绕,下面举个例子,请自行脑补)

a的举例: 超市的储物柜,每个格子都有一个唯一对应的条形码,我们认为这就是每个格子的地址,格子本身就是实实在在的三维空间,是可以放物品的.现在有两个格子a格和b格,我们把背包放进了b格,然后把印有b格条码的纸条放进了a格, 那么,a格就相当于一个指针变量, b格就相当于一个普通的变量. a的意思就是 分两步,第一步找到a格,取出a格中存放的条码,第二步,按照条码找到b格,取出b格中的背包. 最终简化一下, *a就是背包.

1
2
3
4
5
6
7
int b = 666;
int *a;
a = &b;
printf("*a=%d sizeof(*a)=%d\n",*a,sizeof(*a));

**********result**********
*a=666 sizeof(*a)=4

对于间接操作符,需要特别注意的有两点:

第一,间接操作符(*)与用来声明之神类别对象的”*“不属于同种功能,虽然它们两者确实是同一个字符,unicode码点都是 \u002a

第二,间接操作符只作用于指针类型的对象,也就是说简介操作符的的操作数必须是一个指针类型的对象.

“取地址”与”解引用”

通俗的讲,对一个变量的 & 操作称为”取地址”

对一个指针变量的 * 操作称作”解引用”.

换一种角度去理解解引用,”*“的作用是引用指针指向的变量值,引用其实就是引用该变量的地址,“解”就是把该地址对应的东西解开,解出来,就像打开一个包裹一样,那就是该变量的值了,所以称为“解引用”。也就是说,解引用是返回内存地址中保存的值。这个值可以是另一个地址。

多级指针.

如果一个指针变量存的是一个普通变量的地址,比如 int a,只能存放一个整型变量的地址,那么这个指针叫做一级指针;如果一个指针变量存放的是另一个指针变量的地址,比如 int **, 里面存放的是一个(int a)类型的指针变量的地址,那么这个指针变量叫二级指针,依次往下推,可以有三级指针,四级指针….直到多级指针.

函数指针

简单理解就是指向函数地址的指针。比如我们声明的一个C函数

1
2
3
void func(void);
//函数调用
func()

那么对于函数调用表达式 func() 而言, func后缀表达式就已经表示了一个指向返回类型为void,且参数列表为空的函数的指针:

void (*) (void)

函数指针的通用表达形式为:

返回类型 (* cv限定符 )(形参列表)

其中,cv限定符为可选项。

直接上code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void (*p)();
p=&func;

printf("sizeof(func)= %d\n",sizeof(func));
printf("sizeof(p)= %d\n",sizeof(p));

printf("func\t %p = %p\n",&func,func);
printf("p *p\t %p = %p = %p\n",&p,p,*p);

printf("p多次解引用 %p\n", **********************p);

void (*pNull)();
printf("pNull *p\t %p = %p = %p\n",&pNull,pNull,*pNull);
printf("pNull多次解引用 %p\n", **********************p);

**********result**********
sizeof(func)= 1
sizeof(p)= 8
func    0x100000df0 = 0x100000df0
p *p  0x7ffeefbff568 = 0x100000df0 = 0x100000df0
p多次解引用 0x100000df0
pNull *p  0x7ffeefbff560 = 0x0 = 0x0
pNull多次解引用 0x0

可以看出:

  1. 函数指针变量在64位系统下的大小是8,函数指针常量的大小是1。

  2. 函数指针常量中存储的内容是自身的地址(这是为什么函数指针可以被无限次解引用的关键,因为解到最后一层以后,继续解就陷入了”自己找自己“的循环)。

  3. 未赋值的函数指针变量默认指向的是地址是0x0,对存储在该地址的指针对象解引用的结果依旧是0x0,所以可以认为0x0地址的内存单元中存的是地址0x0(此处上可能有误,尚需以后讨论)。

Comments