你如何阅读C语言声明?

48

我听说过一些方法,但都不是很实用。个人尽量避免在C语言中使用复杂数据类型,而是将其分解成组件typedef。

现在我需要维护一些所谓的“三星程序员”编写的遗留代码,我很难阅读其中一些 ***code[][].

你如何阅读复杂的C语言声明?


2
眼睛是有效的...但如果你被困在使用Windows的情况下,我想你可以尝试一下Narrator。 - Shog9
6
“三星程序员”是指他所声明的所有类型中至少有三个星号吗? - Steve Jessop
3
这句话的意思是:“‘pretty much’是用来形容一个C语言程序写得不好的老式侮辱性词语。” - 1729
2
这里有一个网站,可以自动化地为您完成! - Rufflewind
而对于 ***code[][],它会返回 语法错误 :-) 尽管如此,这是一个非常有用的网站。 - Mawg says reinstate Monica
10个回答

38

本文介绍了一个相对简单的7条规则,可以帮助你手动阅读任何C语言声明:

http://www.ericgiguere.com/articles/reading-c-declarations.html

  1. 找到标识符。这是你的起点。在一张纸上写下“声明标识符为”。
  2. 向右看。如果那里什么也没有,或者出现了右括号“)”,则转到步骤4。
  3. 现在你所在的位置要么是数组(左方括号),要么是函数(左圆括号)描述符。可能会有一系列这些描述符,以未匹配的右括号或声明符的结尾(分号或“=”用于初始化)结束。对于从左到右阅读的每个这样的描述符:

    • 如果是空数组“[]”,写下“数组”
    • 如果是带有大小的数组,写下“数组大小为”
    • 如果是函数“()”,写下“返回函数”

    停在未匹配的括号或声明符的末尾,以先到达的为准。

  4. 回到起始位置并向左看。如果那里什么也没有,或者出现了左括号“(”,则转到步骤6。
  5. 现在你所在的位置是指针描述符“*”。可能会有一系列这些描述符在左侧,以未匹配的左括号“(”或声明符的起点结束。从右到左阅读,对于每个指针描述符,写下“指向”的字样。停在未匹配的括号或声明符的起点,以先到达的为准。
  6. 此时你要么有一个带括号的表达式,要么有完整的声明符。如果你有一个带括号的表达式,则将其视为新的起点,并返回步骤2。
  7. 写下类型说明符。结束。
如果你觉得这个工具不错,那么我支持使用程序 cdeclhttp://gd.tuwien.ac.at/linuxcommand.org/man_pages/cdecl1.html

1
似乎有许多cdecl的实现,甚至在K&R书中也有一个。但是它们都只是将其翻译成/从普通英语翻译而来。然而,我想要将其翻译成/从内部表示形式,就像编译器所做的那样。我的意思是,读取C声明,并将其“编码”为某种数据结构。哪种数据结构需要表示任何C声明? - cesss

26

通常我使用右手顺时针法则,也称为“右手螺旋法则”。

  • 从标识符开始。
  • 移到其直接右侧。
  • 然后顺时针移动并到达左侧。
  • 再次顺时针移动并到达右侧。
  • 只要声明没有完全解析,就继续执行此过程。

还有一个要注意的额外元规则:

  • 如果有括号,请先完成每个括号级别,然后再向外移动。

在这里,“前进”和“移动”意味着读取那里的符号。其规则如下:

  • * - 指向
  • () - 返回函数
  • (int, int) - 接受两个int并返回函数
  • int, char等- intchar
  • [] - 数组的
  • [10] - 十个数组的
  • 等等。

因此,例如,int* (*xyz[10])(int*, char)被读作:

xyz是一个

十个数组的

指向

接受int*和char并返回

int*的函数


@sundar: 非常好的参考! - Lazer
1
@sundar:你能扩展它以包括多维数组和函数指针吗?那么extern char *const (*goop( char *b ))( int, long );呢?(选自http://www.ericgiguere.com/articles/reading-c-declarations.html) - Lazer

8

一个词: cdecl

该死,被击败了15秒!


请看我上面的评论。对于 ***code[][],它会返回 语法错误 :-) 尽管如此,这是一个非常有用的网站。 - Mawg says reinstate Monica

4

cdecl提供了一个命令行界面,让我们来试试:

cdecl> explain int ***c[][]
declare c as array of array of pointer to pointer to pointer to int

另一个例子

explain int (*IMP)(ID,SEL) 
declare IMP as pointer to function (ID, SEL) returning int

然而,在书籍《C语言深入秘密》中有一个完整的章节专门讲解这个问题,名为“解析C语言声明”。


4

3

我曾经在做C语言时使用过一个叫做“cdecl”的程序。这个程序似乎在Ubuntu Linux的cutils或者cdecl软件包中,并且很可能也可以在其他地方找到。


1

我刚在 "C语言的发展" 中看到了一个启发性的章节:

对于这种组合类型的每个对象,已经有一种方法来提及底层对象:索引数组、调用函数、在指针上使用间接操作符。类比推理导致了一个声明语法,用于反映名称的表达式语法,其中名称通常出现。因此,
int i,*pi,**ppi;
声明一个整数、一个指向整数的指针和一个指向指向整数的指针。这些声明的语法反映了这样的观察结果:当在表达式中使用i、*pi和**ppi时,它们都产生int类型。同样地,
int f(),*f(),(*f)();
声明返回整数的函数、返回指向整数的指针的函数、返回整数的函数的指针;
int *api[10],(*pai)[10];
声明一个指向整数指针的数组和一个指向整数数组的指针。在所有这些情况下,变量的声明类似于在其类型为声明头部命名的表达式中使用它。

1

1

自动化解决方案是cdecl。

通常,您声明变量的方式与使用它的方式相同。例如,您可以像这样取消引用指针p:

char c = * p

您可以以类似的方式声明它:

char * p;

对于复杂的函数指针也是如此。让我们声明f为“指向返回指针的函数”的好老朋友,并进行外部声明,只是为了好玩。由于它是一个函数指针,因此我们从以下内容开始:

extern * f();

它返回一个指向int的指针,因此在前面有:

extern int * * f(); // XXX not quite yet

现在哪个是正确的结合性?我总是记不住,所以使用一些括号。

extern (int *)(* f)();

以使用它的方式声明它。


0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接