关于声明的螺旋规则 – 什么时候出错?

最近我学习了用于反混淆复杂声明的螺旋规则 , 该规则必须是用一系列typedef写的。 但是,下面的评论警告我:

一个经常被引用的简化,只适用于几个简单的情况。

我没有findvoid (*signal(int, void (*fp)(int)))(int); 一个“简单案例”。 顺便说一句,更令人担忧的是

所以,我的问题是, 在哪种情况下我会正确适用这个规则,哪一个会出错呢?

从根本上说,这个规则根本不起作用,或者它是通过重新定义螺旋的含义来进行工作的(在这种情况下,没有任何意义,例如:

 int* a[10][15]; 

螺旋规则会给出一个int指针数组[10]的数组[10],这是错误的。 它是你的网站的情况下,它也不工作; 事实上,在signal的情况下,甚至不清楚你应该在哪里开始螺旋。

一般来说,find规则失败的例子比find它的例子要容易得多。

我经常试图说parsing一个C ++声明很简单,但没有谁试过复杂的声明的身体会相信我。 另一方面,它并不像有时候那样艰难。 其秘诀在于将声明完全按照您的expression方式来思考,但运算符less得多,而且优先级规则非常简单:右侧的所有运算符优先于左侧的所有运算符。 在没有括号的情况下,这意味着首先处理所有事情,然后处理所有事情,然后按照任何其他expression式处理括号。 实际的困难不是语法本身,而是它的结果是一些非常复杂和违反直觉的声明,特别是涉及函数返回值和指向函数的指针:第一个右边,然后是左边的规则,意味着特定级别的操作符是经常分开,例如:

 int (*f( /* lots of parameters */ ))[10]; 

在这里扩展的最后一个术语是int[10] ,但是在完整的函数说明之后放置[10]是非常不自然的,我必须每次停下来解决这个问题。 (这可能是逻辑上相邻部分分散的趋势导致了螺旋规则,问题当然是在没有括号的情况下,它们并不总是展开 – 只要你看到[i][j] ,规则是正确的,然后再去正确的,而不是螺旋。)

因为我们现在正在考虑用expression式来expression声明:当expression式变得太复杂时,你会怎么做? 您可以引入中间variables以便于阅读。 在声明的情况下,“中间variables”是typedef 。 特别是,我会争辩说,任何时候返回types的一部分在函数参数(以及许多其他时间)之后结束,您应该使用typedef来使声明更简单。 (但是,这是一个“按照我所说的做,而不是按我所做”的规则,恐怕我会偶尔使用一些非常复杂的声明。

规则是正确的。 但是,应用时要非常小心。

我build议以更正式的方式应用C99 +声明。

这里最重要的是识别所有声明的下面的recursion结构( constvolatilestaticexterninlinestructuniontypedef为简单起见从图片中删除,但可以轻松地添加回去):

 base-type [derived-part1: *'s] [object] [derived-part2: []'s or ()] 

是的,就是这样,四个部分。

 where base-type is one of the following (I'm using a bit compressed notation): void [signed/unsigned] char [signed/unsigned] short [int] signed/unsigned [int] [signed/unsigned] long [long] [int] float [long] double etc object is an identifier OR ([derived-part1: *'s] [object] [derived-part2: []'s or ()]) * is *, denotes a reference/pointer and can be repeated [] in derived-part2 denotes bracketed array dimensions and can be repeated () in derived-part2 denotes parenthesized function parameters delimited with ,'s [] elsewhere denotes an optional part () elsewhere denotes parentheses 

一旦你已经分析了所有4个部分,

[ object ]是[ derived-part2derived-part2 / returning)] [ derived-part2 (pointer to)] base-type 1

如果有recursion,你会发现你的object (如果有的话)在recursion堆栈的底部,它将是最内层的一个,你将得到完整的声明,通过恢复和收集每个派生部分recursion水平。

parsing时,可以将[object]移动到[derived-part2] (如果有的话)。 这会给你一个线性的,易于理解的声明(见上面1 )。

因此,在

 char* (**(*foo[3][5])(void))[7][9]; 

你得到:

  1. base-type = char
  2. 等级1: derived-part1 = *object = (**(*foo[3][5])(void))derived-part2 = [7][9]
  3. 等级2: derived-part1 = **object = (*foo[3][5])derived-part2 = (void)
  4. 等级3: derived-part1 = *object = fooderived-part2 = [3][5]

从那里:

  1. 等级3: * [3][5] foo
  2. 等级2: ** (void) * [3][5] foo
  3. 等级1: * [7][9] ** (void) * [3][5] foo
  4. 最后, char * [7][9] ** (void) * [3][5] foo

现在,从右向左阅读:

foo是一个由3个数组组成的5个指针的数组(不带参数),它返回一个指针,指针指向一个由9个指向char的数组组成的数组。

您也可以在每个derived-part2中反转数组维度。

这是你的螺旋规则。

很容易看到螺旋。 你从左边潜入更深的嵌套[object] ,然后在右边重新performance,只有在上层有另一对左右等等。

例如:

 int * a[][5]; 

这不是指向int数组的指针数组。