问题描述:使用较多的嵌入式C语言开发工具是Keil,但程序的开发不仅仅是完成功能就了事这么简单的,后期还需要不断的改善和维护才行,这所花的时间估计会远远大于初步开发完成基本功能所用的时间。为了方便程序的规范、可读、易于维护,模块化设计是很有用处的,那样即使不是代码开发者本人,换成其他人来维护也不至于头疼。下面将记录一些嵌入式C语言工程文件的组织的问题,便于对程序进行模块化,但都比较简单,以后有新的经验再继续更新。
文件组织:我一般分为Output,Listing和source_code。包含这几个子目录的是以程序名命名的文件夹。首先在该文件夹下新建一个工程,添加启动代码。子目录Output是用来存放工程输出文件的,Listing是存放链接.lst文件的,source_code下面就是源代码了,可以根据功能的不同继续进行子目录的创建。
文件引用与对应:这里引用是指如何include不在同一文件目录下的头文件,具体是用"..\"的方式,如source_code文件夹下有a,b两个子文件夹,a存放了应用程序代码,b存放了接口文件,要在a中使用b中接口文件head1.h提供的接口函数时,可以include对应的头文件,由于不在同一文件夹中,可以使用#include"../b/head.h"。其中".."意思是指上层文件夹目录,另外"."指本层文件夹目录。
文件对应是指在工程选项中将Output指定为输出文件存放的文件夹,将Listing指定为链接文件存放的文件夹。
如何包含接口文件:这里的接口文件就是指c里面的.h头文件。uCOS中建议的方法是用一个INCLUDE.h将所有头文件都包含进来,这样就不必详细的根据模块的需要来一个个包含了,uCOS中指出该方法的唯一缺点是编译时间要更长。但是总觉得这样对c的模块化还是不太有利的,最好还是详细知道各个接口文件都提供了什么,然后根据自己的需要去一个个的包含,虽然这样更耗时。
模块:C程序设计需将其看作是一些独立的模块。模块是一组服务的集合,其中一些服务可以被程序的其他部分使用。每个模块都有一个接口来描述所提供的服务。模块的类型可以分为:数据池、库、抽象对象、抽象数据类型(ADT)。
对于不同功能的模块,尽量用一个.c和一个.h文件划分该模块,.h文件用于一些宏定义、全局函数的声明,.c文件中就是具体的代码实现了,包括子函数、变量等。这样做能重用各模块,针对不同的应用程序只需要调用相应模块实现功能即可,不用从头到尾又一遍的重新编写各个功能的实现。
一个嵌入式系统一般包括两类模块:硬件驱动模块(针对一种特定硬件的模块)和软件功能模块(保持低耦合、高内聚要求)。
模块接口的设计不是随意的声明集合,需要考虑高内聚性与低耦合性。高内聚性指模块中元素应彼此紧密相关,使模块更易于使用,使程序更易于理解。低耦合性指模块之间应尽可能相互独立,使程序更易于修改,方便复用模块。
模块的设计:
一.头文件编写基本原则:
1.头文件中不能有可执行代码,也不能有数据的定义,只能有宏、类型(typedef,struct,union,menu),数据和函数的声明。
这个很好理解,只进行宏定义、数据类型和函数的声明不会开辟内存,声明是编译器需要。而在头文件中定义则需要开辟内存的,这样导致每个包含该接口的文件都分配一次内存了。
2.头文件中不能包含本地数据(模块自己使用的数据或函数,不被其他模块使用)。
这一点相当于面向对象程序设计里的私有成员,即只有模块自己使用的函数,数据,不要用extern在头文件里声明,只有模块自己使用的宏,常量,类型也不要在头文件里声明,应该在自己的*.c文件里声明。这也是称.h文件为接口声明的原因,好的模块设计使使用者只需关注.h文件提供的接口进行调用即可。
3. 含一些需要使用的声明。
在头文件里声明外部需要使用的数据,函数,宏,类型。
4. 防止被重复包含。
使用#ifndef宏防止一个头文件被重复包含。
二、头文件编写参考更多的规则
1. 一个模块一个接口,不能几个模块用一个接口。
即模块要进行很好的内聚性设计,每个模块相对独立。如果多个模块一个接口就会显得比较混乱。
2. 文件名为和实现模块的c文件相同。
如abc.c--abc.h,这样便于程序的设计和维护,一目了然的知道某一个模块。
3. 尽量不要使用extern来声明一些共享的数据。
因为这种做法是不安全的,外部其他模块的用户可能不能完全理解这些变量的含义,最好提供函数访问这些变量。全局变量的使用特别在工程代码很多的时候容易出问题,许多时候在任何地方都有可能多次对全局变量进行了改变,而且是在不同的函数模块中,这样编程时查找或调试都会有许多问题。使用函数进行访问,即是参考了面向对象中封装的思想,一个函数get获取,一个set函数设置,查找起来也方便。
4. 尽量避免包含其他的头文件,除非这些头文件是独立存在的。
这一点的意思是,在作为接口的头文件中,尽量不要包含其他模块的那些暴露*.C文件中内容的头文件,但是可以包含一些不是用来暴露接口的头文件。在实际使用中这样会导致编译器一些莫名的问题,有时可能是重复定义错误,有时可能是缺少定义错误。
5.不要包含那些只有在可执行文件中才使用的头文件,这些头文件应该在*.c文件中包含。
这一点如同上一点,为了提高接口的独立性和透明度。
6. 接口文件要有面向用户的充足的注释。
从应用角度描述个暴露的内容。
7. 接口文件在发布后尽量避免修改,即使修改也要保证不影响用户程序。
三、多个代码文件使用一个接口文件
这种头文件用于那些认为一个模块使用一个文件太大的情况。对于这种情况在参考上述建议后,也要参考以下建议。
1.多个代码文件组成的一个模块只有一个接口文件。因为这些文件完成的是一个模块。
2. 使用模块下文件命名 <系统名><模块名命名>
3. 不要滥用这种文件。
4.有时候也会出现几个*.c文件用于共享数据的*.h文件,这种文件的特点是在一个*.c文件里定义全局变量,而在其他*.c文件里使用,要将这种文件和用于暴露模块接口的文件区别。
5.一个模块如果有几个子模块,可以用一个*.h文件暴露接口,在这个文件里用#include包含每个子模块的接口文件。
四、说明性头文件的使用
说明性头文件,这种头文件不需要有一个对应的代码文件,在这种文件里大多包含了大量的宏定义,没有暴露的数据变量和函数。
1. 包含一些需要的概念性的东西。
2. 命名方式,定义的功能.h
3
.不包含任何其他的头文件。
4. 不定义任何类型。
5. 不包含任何数据和函数声明
五、C代码文件.c的使用建议
*.c文件是C语言中生成汇编代码和机器码的内容。
1.命名方式 模块名.c
2,用static修饰本地的数据和函数。
3,不要使用extern
这是在*.h中使用的,可以被包含进来。
4,无论什么时候定义内部的对象,确保独立与其他执行文件。
5,这个文件里必须包含相应功能函数。
模块化设计可以参考更多的面向对象和COM的思想。