C 语言:莫名其妙的声明
Apr 15, 2025
我最近在看《C 专家编程》。不得不承认这是本好书,探讨了很多我困惑很久,而未能够得到解答的疑问。其中一章详细讲解了 C 语言奇怪的声明语法:声明不能简单地从左往右阅读,必须理解操作符的优先级规则,时而要从右往左,时而要跳跃解读,蹦来蹦去。
为什么设计成这样呢?据说是为了让声明形式和使用形式保持一致,举例来说,你可能有如下的声明:
int (*(*foo)(void))[3];
使用起来是这样:
int (*ptr_to_arr)[3] = (*foo)();
怎么样,是不是突然感觉有点道理了?
书中还介绍了一个实用程序 cdecl
,它的作用是将 C 语言声明转写成人类语言的形式,例如刚刚的声明用我的 cdecl
程序解析,输出如下:
foo is pointer to function (void) returning pointer to array 0..2 of int
怎么样,有意思吧?这里还有个在线版本:cdecl.org
不过说来话长,这个小程序还是挺不好写的,按照书上的思路,你要先把标识符(你可以理解成变量名)之前的所有符号压入一个堆栈中,然后开始处理标识符后面的符号,如 []
和 ()
。
不过鉴于括号有多义性(一方面可以改变结合的优先级,另一方面代表此处是一个函数)、函数参数可以套娃指针参数等等问题,对我来说实际写起来难度不小。
书上的参考实现里使用到了标准库中的 ungetc
函数,这个函数出现在 get_token
函数里。
void get_token(void) {
char *p = this.string;
/* 略过空白字符 */
while ((*p = getchar()) == ' ')
;
/* 读入的标识符以A-Z,0-9开头 */
if (isalnum(*p)) {
while (isalnum(*++p = getchar()))
;
ungetc(*p, stdin);
/* 前面的循环会误食到不应该读的、非纯数字字母字符,
* 这里用 ungetc 吐回去 */
*p = '\0';
this.type = classify_string();
return;
}
/* 标识符为指针 */
if (*p == '*') {
strcpy(this.string, "pointer to");
this.type = '*';
return;
}
/* 标识符为括号或逗号 */
this.string[1] = '\0';
this.type = *p;
return;
}
简单来说,ungetc
函数可以把字符放回标准流中,这样就不需要再弄一个变量记录前一个读取的字符,还是挺有用的。
我照抄了网上的实现,不过加上了解析函数参数列表的功能,虽然还有 Bug,我也懒得再改了。以后的事情,以后再说吧(