浅谈C语言变量声明的解析

C语言本身提供了一种不甚明确的变量声明方式——基于使用的声明,如int *a,本质上是声明了*a的类型为int,所以得到了a的类型为指向int的指针。对于简单类型,这样声明并不会对代码产生多大的阅读障碍,而对于复杂的声明,比如标准库的signal函数签名,void (*signal( int sig, void (*handler) (int))) (int),这是什么?一眼看不出来吧,这是一个函数,接受两个参数,一个int,一个函数指针,而这个函数指针指向的函数接受一个int并返回void;返回一个函数指针,这个函数指针指向的函数接受一个int并返回void。尽管很多人都吵着说工程中谁写出来这样的代码就炒他鱿鱼,但人家标准库的确是写出了这样的代码的,你怎么办?

解析这样的复杂声明,我之前见过一种方法——右旋转法,方法是这样的:

  • 从变量名开始,先右再左地,交替地一个一个向外看旁边的token,在纸上写下:“变量是”
  • 若向右遇到左圆括号,在纸上写下:“函数,参数是”,并用同样的方法处理括号中每一个参数——在纸上写下:“返回”
  • 若向右遇到方括号,在纸上写下:“数组,长度为{方括号的内容},元素类型为”
  • 若向右遇到右圆括号,什么也不做
  • 若向左遇到*,在纸上写下:“指针,指向”
  • 若向左遇到任何类型,在纸上写下对应的类型名

我们用这种方法来处理下面的声明

void*(*(*fp1)(int))[10]
  • 从fp1开始——fp1是
  • 向右,遇到右括号,什么也不做
  • 向左,遇到*——指针,指向
  • 向右,遇到左圆括号——函数,参数是int,返回
  • 向左,遇到*——指针,指向
  • 向右,遇到左方括号——数组,长度为10,元素类型为
  • 向左,遇到*——指针,指向
  • 向右,已经到声明结尾,什么也不做
  • 向左,遇到void——void

结果是:fp1是 指针,指向 函数,参数是int,返回指针,指向数组,长度为10,元素类型为 指针,指向 void

这种方法对于人来讲是比较合适的,因为他比较符合人脑的处理方式,但是也有一点缺点,如果函数的形参也写了名字,不是很熟练的小白,就不容易找到正确的起始位置,造成处理的混乱。

对于机器处理,这种从中间到两边的方法就不是很合适了,因为机器并不能直接在一个token序列中直接找到处理的起始位置,他只能从左到右进行扫描,我昨晚灵机一动想到一个算法,今天进行了试验,效果良好,没有对比一些比如cdecl.org那样的开源实现,我这个算法只是一个demo,并不完整支持C声明的处理,地址在这里

算法从左到右扫描,本质上是递归下降,基本过程是这样的,为了方便说明,递归函数名为parse:

  • parse开始
  • if遇到类型,保存进变量a,递归parse,输出a
  • elif遇到*,递归parse,输出"pointer to"
  • elif遇到左圆括号,递归parse,并检查括号匹配
  • elif遇到标识符,输出"{标识符} is"
  • 控制流继续
  • if遇到左方括号,输出"array with length {长度} of",并检查括号匹配
  • elif遇到左圆括号,输出"function accepting",循环地递归parse,吃掉后面的逗号,直到遇到右圆括号,输出"returning"
  • 返回

递归的意义是什么?每一个parse函数的意义都是:“我处理的这段东西的类型是——”,破折号后面的东西右这一层函数退出后上一层函数来补完,所以回去看上面的算法,遇到一个类型的时候,我就明白我下面一层处理的这段东西的类型是int,所以我递归调用parse,并输出int。

再从循环不变式的角度看parse的递归,parse的出口只有一个,那就是遇到的第一个if,不管是什么样的函数声明,最后都是以一个变量结尾的,而这里也是唯一一个把话说全了的分支——其他的分支都是输出类似于“xxx是”,“xxx返回”这样的没说完的话的,所以parse保证上一层的话都没有说完——从不是第一个if的分支退出,由这一层把话补全,这算某种意义上的循环不变式吧。

最后我把上面的声明拆成不同层数来表现一下parse的过程

void
    *              [10]
     (            )
      *      (int)
       (    )
        *
         fp1
时间: 2024-12-23 13:50:13

浅谈C语言变量声明的解析的相关文章

浅谈C语言中的强符号、弱符号、强引用和弱引用

摘自http://www.jb51.net/article/56924.htm 浅谈C语言中的强符号.弱符号.强引用和弱引用 投稿:hebedich 字体:[增加 减小] 类型:转载 时间:2014-10-31 我要评论 这篇文章主要介绍了C语言中的强符号.弱符号.强引用和弱引用的定义及相关内容,非常的简单易懂,有需要的朋友可以参考下 首先我表示很悲剧,在看<程序员的自我修养--链接.装载与库>之前我竟不知道C有强符号.弱符号.强引用和弱引用.在看到3.5.5节弱符号和强符号时,我感觉有些困惑

浅谈c语言typedef 与结构体指针(个人小经验)

 #include<stdio.h> #include<string.h> typedef struct emp{ char sex[8]; char name[15]; int age; }*emp;//这里我们用typedef把emp这个结构体变成了*emp这种指向结构体成员的结构体指针 /*typedef struct emp{ char sex[8]; char name[15]; int age; }pi,*emp;//为了程序的可读性最好不要这样声明*/ int m

浅谈C语言中的联合体(转载)

联合体union 当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union).在C Programming Language 一书中对于联合体是这么描述的: 1)联合体是一个结构: 2)它的所有成员相对于基地址的偏移量都为0: 3)此结构空间要大到足够容纳最"宽"的成员: 4)其对齐方式要适合其中所有的成员: 下面解释这四条描述: 由于联合体中的所有成员是共享一段内存的,因此每个成员的存放首地址相对于于联合体变量的基地址的偏移量为0,即所有成员的首地址都是一样的.为

C语言变量声明内存分配

转载: C语言变量声明内存分配 一个由c/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)— 程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈.程序结束时由编译器自动释放. 2.堆区(heap) — 在内存开辟另一块存储区域.一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 .注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵. 3.全局区(静态区)(static)—编译器编译时即分配内存.全局变量和静态变量

浅谈C语言字符串结束符&#39;\0&#39;

如果你希望你的字符串以’\0‘结束,那么你可以这样做: 1 char str[]={"hello"};//①字符串赋值 2 char str[]={'h','e','l','l','o','\0'};//②人为添加 3 char str[6]={'h','e','l','l','o'};//③故意给数组预留一个空位 注:当出现以下情况时,会发生'\0'丢失 1 char str[5]={"hello"};//①数组长度不够 2 char str[]={'h','e'

C语言变量声明问题——变量定义一定要放在所有执行语句/语句块的最前面吗?

报错信息:error C2065: 'salary' : undeclared identifier #include <stdio.h> void main(){ printf("我的成绩是100分!");//不使用变量 int salary;//使用变量 salary = 100; printf("我的成绩是%d分!",salary); return 0; } 问题根源:编译器问题——C89和C99 C89规定,在任何执行语句之前,在块的开头声明所有

编程之美,让美国人科技高速发展,浅谈C语言带给美国的变化

我去年7月份有幸应美国朋友的邀约,在美国众多正在飞速发展中的高科技型企业畅游了一番.本来我以为,美国只有Google公司,苹果公司,FaceBook,IBM,微软,思科这些巨型的高新技术企业在世界的新技术领域活跃.然而真正的到了美国,我才真正的发现美国远远比我想象的更加丰富多彩.而让美国高新科技产业展现蓬勃生机的因素,我深深的发现C语言确实功不可没. C语言是一门通用计算机编程语言,广泛应用于底层开发.C语言的设计目标是以一种简易的方式编译.处理低级存储器,产生少量的机器码以及不需要任何的运行环

浅谈C语言嵌入式系统编程注意事项

C语言嵌入式系统编程注意事项之背景篇 本文的讨论主要围绕以通用处理器为中心的协议处理模块进行,因为它更多地牵涉到具体的C语言编程技巧 不同于一般形式的软件编程,嵌入式系统编程建立在特定的硬件平台上,势必要求其编程语言具备较强的硬件直接操作能力.无疑,汇编语言具备这样的特质.但是,归因于汇编语言开发过程的复杂性,它并不是嵌入式系统开发的一般选择.而与之相比,C语言--一种"高级的低级"语言,则成为嵌入式系统开发的最佳选择.笔者在嵌入式系统项目的开发过程中,一次又一次感受到C语言的精妙,沉

浅谈类加载器与类加载案例解析

一.示意图 注:Car car2 = new Car; 其中car1作为引用类型变量,保存在Java栈,而对象本身保存在堆中.类加载器只负责将.class文件加载到内存中,此后JVM将根据这个数据文件封装成对应的数据结构(类对象),虽然类对象也是对象,但是HotSpot虚拟机将其放在了方法区中. 二.类加载器——双亲委托机制和沙箱安全 类加载器分为四类(主要为三类):启动类加载器.扩展类加载器.应用类加载器和自定义类加载器,本文不对自定义类加载器做介绍. 你有没有想过,问什么你没有定义过Stri