第三章 TinyOS编程语言nesC
第一节 C与nesC的比较
2.nesC与C的主要区别
由上述应用Powerup的C实现版本与nesC实现版本可以得出C与nesC的不同。
1)C程序由函数组成;相对的,nesC程序由组件组成,组件实现特定的服务。
2)C函数通过直接彼此调用来进行交互;组件之间交互由接口指定。接口的使用者对接口的提供者提出请求,提供者回调(通知事件)接口的使用者。接口是一组相关函数的集合,其中的命令与事件类似常规函数,包含标准的C代码。调用命令与通知事件就是函数调用。PowerupC是Boot和Leds的使用者,启动事件是一个回调函数,通知系统启动。Led0On是命令,请求打开LED0。nesC接口与Java的接口类似,添加了关键字command和event来区分请求与回调。
3)PowerupAppC是C与nesC之间最大的不同。换句话说,nesC程序使用连接显式地指定组件之间如何交互。C程序中调用的是一个全局名字Led0_on。而nesC程序中由编程者来显示选择使用哪个组件的函数实现,比如,组件PowerupAppC中的连接语句显式地指定了PowerupC的Leds接口连接到组件LedsC上。将组件之间的连接交给编程者决定可以带来2个好处。
·在编程过程中往往需要切换调用的功能模块,由编程者来决定组件之间的连接关系,会简化具体实现代码。
我们尝试改变下面两行代码:
Components PowerupC,NoLedsC;
PowerupC.LedsC->NoLedsC.Leds;
这种更改将连接切换到组件NoLedsC上,没有影响nesC程序的其他部分。但是,对于C的实现代码而言,如果使用别的版本替换掉Powerup中使用的mote库,则会影响所有的LED用户,不仅仅是应用Powerup自己。
·提供了一个支持回调的有效机制
考虑计时器的例子。在计时器到时时,要回调与该计时器相关的触发函数。C实现中为了从计时器函数回到主调函数,需要使用函数传递运行时的参数。在nesC实现中,计时器组件与PowerupC组件之间的连接是在编译时指定的,因此不需要函数指针,既节省了RAM,也允许nesC编译器执行跨组件的回调函数的优化。
总而言之,nesC与C的主要不同在于组件、接口和连接。这些都与程序元素(变量、函数与类型等)的命名和组织有关。
C与C++的全局命名范围需要动态组织程序。对于函数调用,连接程序的工作就是对应上全局命名范围中那个唯一的函数名字。如果多个程序块引用同一个函数,在这些程序块之间会引入潜在的依赖性。针对这个问题,C和C++分别使用了函数指针或工程等间接方式,将函数的不同实现区分。
nesC中采用了不同的方式——静态连接。nesC是基于组件的C语言。组件是一段段具有一定功能的代码块。在某些方面,nesC组件与对象相似,比如,封装状态、将状态与功能合到一起。它们的主要区别在于命名范围,C++对象引用在全局命名范围的函数和变量;而nesC的组件名字是全局的,但组件内部的操作是局部的。组件仅能引用自己局部范围(组件范围)内的函数和变量,不能引用别的组件范围内的函数和变量。为此,组件不仅要声明它提供(实现的可供其他组件使用)的函数,也要声明它要使用的函数。由于组件用来调用函数的名字是完全局部的,它引用函数使用的名字不必与实现函数的名字相同。组件A声明提供函数b,实际上是将A.b引入到全局命名范围,另一个组件C提供函数b即将C.b引入到全局命名范围。也就是说,即便某个组件D想要引用函数b,也可能引用完全不同的实现。
表3-1中总结了C、C++与nesC在组织程序方面的不同。
表3-1 C、C++与nesC的比较
C语言中的编程单位是文件,由相关头文件来规范文件行为。连接程序通过匹配文件内的全局元素名字来构建应用。用这种连接方式来构建程序是不够的,因此,编程者还需使用函数指针的方式支持延迟调用的函数。
C++提供了组织程序的显示机制。编程单位是类,类将相关函数组成组,程序由一组交互的对象(类的实例)组成。抽象类用于定义通用的类模式,类从抽象类中继承并实现它的方法。连接程序通过匹配类和函数名字来构建应用。相对于C中的函数指针,虚拟方法提供了更加便利和结构化的方式。
nesC程序由相互协作的组件组成。每个组件使用接口来指定它提供和使用的服务。编程者编写连接语句来连接组件以构建应用。由于使用显式连接语句,不依赖于隐式的名字匹配,因此,不需要使用如函数指针、虚拟方法的动态机制。