1、概述:
在C语言中对于宏的使用,使得软件开发中代码可读性提高(或许有人认为宏的使用使得可读性变差了)的同时,性能和可维护性也随之提高了。**为什么说可读性提高了?**①宏定义使得常量的使用意义更为清晰;②宏函数封装减少重复代码块,实现复用。**为什么又说性能提高了?**①宏函数区别于普通函数的一点就在于宏函数做预处理替换,而普通函数调用需要额外的压栈出栈操作,频繁的压栈出栈操作自然会降低性能(同时耗费资源)。②宏函数的迷人之处就在于:其均衡了函数封装引起的效率降低资源侵蚀问题与代码块过长引起的可读性、可维护性差的问题。
那么可维护性是函数封装带来的直接益处之一,宏函数自然也具备该特点。就其优点是十分明显,而其缺点亦是十分突出:宏只是简单替换,并不会检查语法错误、参数类型匹配等问题,如果对于宏的使用不是十分熟悉(即使很熟悉),对其优缺点所带来的利弊性就很难掌握。
今天我们就避其缺点,就其优点,针对宏定义(以"##"为核心)在驱动开发中所发挥的无可替代的作用进行简单的分析。过后,宏的魅力或许能够在各自的心中留下或多或少的体会。
关于宏的基本知识点可参考博客:C语言宏定义、宏函数、内置宏与常用宏
2、驱动dispatch(思路来源于BCM 的 SDK源码):
该例中,以swap()函数的不同实现对外呈现同一接口为例,以驱动屏蔽底层细节为要点来展现“##”所发挥的作用。
(1)、代码结构:
①main.c:作为实际的上层(用户层);
②dispatch.c:中间的驱动适配分发层;
③mode_one.c和mode_two.c则是底层的驱动实现层;
(2)底层实现(以one和two代表不同的硬件驱动实现):
/* mode_one.c */
void function_one_swap(int * v1, int * v2)
{
if(v1 == v2 || !v1){
return;
}
int temp = *v1;
*v1 = * v2;
*v2 = temp;
return;
}
/* mode_two.c */
void function_two_swap(int * v1, int * v2)
{
if(v1 == v2 || !v1){
return;
}
*v1 ^= *v2;
*v2 ^= *v1;
*v1 ^= *v2;
return;
}
(3)中间适配与驱动分发:
/* mode_one.h */
#ifndef __MODE_ONE_H__
#define __MODE_ONE_H__
#define MODE_ONE_DRIVER_SUPPORT
#endif /* __MODE_ONE_H__ */
/* mode_two.h */
#ifndef __MODE_TWO_H__
#define __MODE_TWO_H__
#define MODE_TWO_DRIVER_SUPPORT
#endif /* __MODE_TWO_H__ */
/* mode_define.h */
#ifndef __MODE_DEFINE_H__
#define __MODE_DEFINE_H__
#include "mode_one.h"
#include "mode_two.h"
#endif
/******************************************************************
* mdoe_list.h,该文件是结合“##”使用的核心
* 注意该文件虽是头文件,但是不要用普通头文件的格式
* #ifdef...#define...#endif来处理
******************************************************************/
#ifdef FUNCTION_DISPATCH
#ifdef MODE_ONE_DRIVER_SUPPORT
FUNCTION_DISPATCH(one)
#endif /* MODE_ONE_DRIVER_SUPPORT */
#ifdef MODE_TWO_DRIVER_SUPPORT
FUNCTION_DISPATCH(two)
#endif /* MODE_TWO_DRIVER_SUPPORT */
#undef FUNCTION_DISPATCH
#endif /* FUNCTION_DISPATCH */
/*******************************************************************
* dispatch.h,提供给中间适配层和上层的头文件
* 根据实际的,现有的不同模块(mode_define.h)
* 定义了模块对应的枚举类型
*******************************************************************/
#ifndef __DISPATCH_H__
#define __DISPATCH_H__
#include "mode_define.h"
#define FUNCTION_DISPATCH(type) \
modeType_##type,
typedef enum{
#include "mode_list.h"
modeType_max,
modeType_error = -1
}MODE_TYPE;
int swap(int * v1, int * v2, MODE_TYPE type);
#endif
/* dispatch.c,驱动分发的具体处理 */
#include "dispatch.h"
#include "mode_define.h"
/* extern声明 */
#define FUNCTION_DISPATCH(type) \
extern int function_##type##_swap(int *, int *, MODE_TYPE);
#include "mode_list.h"
/* 获取函数地址,统一放入swap驱动数组 */
#define FUNCTION_DISPATCH(type) \
function_##type##_swap,
static int (* function_swap[])(int *, int *, MODE_TYPE) = {
#include "mode_list.h"
0
};
int swap(int * v1, int * v2, MODE_TYPE type)
{
if(!v1 || !v2){
return -1;
}
if(type < modeType_error && type > modeType_max){
return -1;
}
function_swap[type](v1, v2, type);
printf("call driver addr:%#X\n", function_swap[type]);
return 0;
}
(4)上层调用:
#include <stdio.h>
#include "dispatch.h"
int main(void)
{
int a = 111, b = 222;
printf("a:%d\tb:%d\n", a, b);
/* 调用者使用的是与mode one对应的“硬件”,会适配到modeType_one对应的“驱动” */
MODE_TYPE type1 = modeType_one;
swap(&a, &b, type1);
printf("a:%d\tb:%d\n", a, b);
/* 调用者使用的是与mode two对应的“硬件”,会适配到modeType_two对应的“驱动” */
MODE_TYPE type2 = modeType_two;
swap(&a, &b, type2);
printf("a:%d\tb:%d\n", a, b);
return 0;
}
测试结果:
3、分析:
对于dispatch.c其预处理之后(gcc -E -o dispatch.i dispatch.c),得到部分内容如下,根据所有的底层驱动实现,来确定中间层能够提供的分发的枚举中的type,以及驱动数组中的成员:
如果,我们要增加一个新的“硬件”类型,则的“硬件驱动”实现后,在mode_define.h中增加该新类型的头文件(比如#include "mode_three.h"等),然后在mode_list.h中增加对该类型的适配,如下:
#ifdef FUNCTION_DISPATCH
#ifdef MODE_ONE_DRIVER_SUPPORT
FUNCTION_DISPATCH(one)
#endif /* MODE_ONE_DRIVER_SUPPORT */
#ifdef MODE_TWO_DRIVER_SUPPORT
FUNCTION_DISPATCH(two)
#endif /* MODE_TWO_DRIVER_SUPPORT */
#ifdef MODE_THREE_DRIVER_SUPPORT
FUNCTION_DISPATCH(three)
#endif /* MODE_THREE_DRIVER_SUPPORT */
#undef FUNCTION_DISPATCH
#endif /* FUNCTION_DISPATCH */
只要底层驱动按照规格来写,适配层按照底层规格来适配,上层按照适配层规则来调用,则该代码是易于扩展的,分为横向扩展:新功能(如每个模块新增add函数,只需要在dispatch.c中增加对应的适配处理即可),和纵向扩展:新类型(类似于FUNCTION_DISPATCH(three)的添加方法一样)。横向扩展以add为例,add函数有两个参数一个返回值,只要底层模块实现完成,则在dispatch.c中增加以下代码即可:
#define FUNCTION_DISPATCH(type) \
extern int function_##type##_add(int *, int *, MODE_TYPE);
#include "mode_list.h"
#define FUNCTION_DISPATCH(type) \
function_##type##_add,
static int (* function_add[])(int *, int *, MODE_TYPE) = {
#include "mode_list.h"
0
};
int add(int * v1, int * v2, MODE_TYPE type)
{
if(!v1 || !v2){
return -1;
}
if(type < modeType_error && type > modeType_max){
return -1;
}
function_add[type](v1, v2, type);
printf("call driver addr:%#X\n", function_add[type]);
return 0;
}