在阅读优秀的 c 语言开源程式的时候,我们经常会看到各种复杂的声明,顿时会让我们怀疑人生,怀疑自己是否真的看得懂 c 语言。然而冷静三秒钟,透过现象看本质,发现牛人写的代码并不是“天书”, 也是很好懂的,关键是要冷静和耐心去阅读。

从“C Traps and Pitfalls”中的一个例子说起

(*(void(*)())0)()

下面我们来一步步分析:

我们知道变量的声明方式为:

int a;

函数的声明方式为:

int fn();

指针的声明方式为:

int *a = 10;

函数指针的声明方式为:

int (*fn)(); //fn 是一个指向返回int型的函数的指针

函数指针的调用方式:

typedef int (*fn_type)();

int fn() {
    return 0;
}

fn_type a = fn;

printf("%d\n", a());
//或者
printf("%d\n", (*)a());

最简单的类型转换:

void *p = 10;
(int *)p;

在 c 语言中,去掉变量名,就是变量类型:

int (*fn)();//fn 是一个指向返回int型的函数的指针
int (*)(); //表示指向返回int型的函数的指针类型

// 还可以用typedef

typedef int (*fn_type)(); // fn_type 就是指向返回int型的函数指针类型

回到上面的声明,我们先分析里边的部分(void(*)())0,很显然void (*)()是一个指向返回 void 类型的函数的指针类型,可以简化为typedef void (*fn_ptr)(); (fn_ptr)0, 这样写是不是一下子明了了许多,原来是把0强制类型转换成了fn_ptr类型,也就是把0转成了一个指向返回void的函数的指针类型。这样的话,原式可以等价为((* fn_ptr )0)(), 实际上就是一个先转型,后调用的过程,即先把 0 转成函数指针,然后再调用函数。

再来看一个例子

char *(*(*a[])())()

有了前面的基础,我相信再理解这个声明就没有那么困难了。

首先还是从内到外来解读:

(*a[])()

很显然,在 c 语言中,去掉变量名就是变量类型,这里的 a 是一个指向函数的指针的数组,我们将其看做一个整体a0,那么再到外层为:

char *(*a0)()

这样就很显然了,a0是一个指向返回char *类型的函数的指针。

那么综合起来解读这个声明即为:a 是一个指向 返回一个 指向返回char *类型的函数指针类型 的函数指针类型的数组。

哈哈,是不是读起来很绕口,很多时候确实是这样的,为了便于理解,也可参考下面的代码:

#include <stdio.h>

typedef char * (*f1_ptr)();
typedef f1_ptr (*f2_ptr)();

char *f1() {
    return "hello";
}

f1_ptr f2() {
    return f1;
}

int main(int argc, char *argv[])
{
    char *(*(*a[1])())();
    a[0] = f2;
    printf("%s liubang\n", (a[0]())());
    return 0;
}

此外,我们还可以借助一些很好用的开源工具来帮我们解读这些声明,而且有时候,用英文表达这些声明能更好的帮我们解:

liubang@venux:~$ sudo apt-get install cdecl -y
liubang@venux:~$ cdecl
Type `help' or `?' for help
cdecl> explain char *(*(*a[])())()
declare a as array of pointer to function returning pointer to function returning pointer to char
cdecl> declare a as pointer to function returning struct tag
struct tag (*a)()