C语言声明的螺旋法则

最近在B站看到了“顺时针/螺旋法则”(Clockwise/Spiral Rule),又称螺旋法则,这一法则主要用于解析C语言中的复杂声明,而平时接触的复杂声明莫过于变幻莫测的指针,尤其是那些函数指针。法则的内容十分简单,它将声明解析分为了三个步骤:

  1. Starting with the unknown element, move in a spiral/clockwise direction; when ecountering the following elements replace them with the corresponding english statements:

    从未知变量开始,按顺时针螺旋顺序阅读,当遇到以下元素替换为相应的自然语言表达:

    • [ ] or [] => Array X size of… or Array undefined size of…
      => 长度为X或者长度未知的……类型数组

    • (type1, type2) => function passing type1 and type2 returning…
      => 传入参数类型为type1和type2、返回值类型为……的函数

    • * => pointer(s) to…
      => 指向……类型的指针

  2. Keep doing this in a spiral/clockwise direction until all tokens have been covered. 重复顺时针螺旋法则直到读完所有元素。

  3. Always resolve anything in parenthesis first! 优先处理括号内内容!

应用上述法则,可以尝试处理以下声明:

1
2
3
4
5
6
7
8
9
10
11
# from K&R2e
int *f();
int *f;
int (*pf)();
char **argv;
int (*daytab)[13];
int *daytab[13];
void *comp();
void (*comp)();
char (*(*x())[])();
char (*(*x[3])())[5];

应用顺时针/螺旋法则解读这类复杂声明不仅简单有效,而且由来已久,早在1994年就有人提出。一般具体使用时,会用 typedef 语法替代复杂的指针声明,提高代码的可读性,C++11中 using 也有等效于 typedef 的用法,一定程度解决了C语言 typedef 不兼容C++ template 的问题,例如下面的代码:

1
2
3
# from zhihu
template<typename T>
using Ptr = int (*)(std::vector<void (T::*)(std::array<int*, 10>*[10])>*, int**&);

关于这种声明,Ptr类型的解读就非常微妙,《C陷阱与缺陷》有一个类似的例子—— (*(void(*)())0)(),这个例子是的作用是在计算机开机时由硬件显式调用首地址为0的子例程。虽然看上去比较复杂,但C语言的声明都存在 [type declarator] 结构,如果我们对前面的代码进行分析,可以得出以下结构:

1
2
3
4
5
6
7
8
9
10
11
# from K&R2e                           // typedef 类型定义    declarator 变量声明
int *f(); // int *() f
int *f; // int * f
int (*pf)(); // int (*)() pf
char **argv; // char** argv
int (*daytab)[13]; // int (*)[] daytab
int *daytab[13]; // int *[] daytab
void *comp(); // void *() comp
void (*comp)(); // void (*)() comp
char (*(*x())[])(); // char (*(*())[])() x
char (*(*x[3])())[5]; // char (*(*[])())[] x

对照 (*(void(*)())0)() 和上述typedef应该可以发现,void(*)() 似乎和 int (*)() 类似,假设我们称 void(*)() 类型为PF(aka. typedef void(*)() PF;),那么原式应该为 (*(PF)0)(),于是我们可以发现 (PF)0 似乎是将0转化为PF类型,假设 PF pf = (PF)0,那么原式就等于 (*pf)()。回想我们对于函数的定义和调用,int main(); 的调用方法就是 main();,那么在这里同理 (*pf)() 实际上也是调用了定义为 type_unknown (*pf)(); 的函数。回过头思考前面那个声明,Ptr 实际上也是上述typedef的同类,关键还是知道未知变量declaration原来在typdef的哪里,了解清楚这一要点,可以将原式转化为以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Ptr 是指向接受type1和type2参数、返回值为int类型的函数的指针
template<typename T>
using Ptr = int (*)(std::vector<void (T::*)(std::array<int*, 10>*[10])>*, int**&);

// type1 是指向std::vector<type3>类型的指针
template<typename T>
using type1 = std::vector<void (T::*)(std::array<int*, 10>*[10])>*;

// type2 是指向指向int类型的指针的指针的引用
typedef type2 = int**&;

// type3是指向接受type4参数、返回值类型为void的T类成员函数的指针
template<typename T>
using type3 = void (T::*)(std::array<int*, 10>*[10]);

// type4是长度为10的指向std::array<int*, 10>类型的指针的数组
typedef std::array<int*, 10>*[10] type4;