自从2019年2月17日断更至今,已有9个月,一方面是转行成功,从硬件可靠性测试行业转入IT,目前在从事的也是自己喜欢的C语言领域,另一方面,学海无涯,沉迷于学习无法自拔......早前制定的1000小时计划早已达成,正往2000小时方向前进,今天开始接着进行更新,进入主题,本文主要讲C开发中的维测手段,使用宏完成重复文本的替换。
由于本人经常进行一些算法练习,所以在工作区间中,经常会使用到错误处理,即printf或者fprint来打印错误日志,如下:
int insert_circle_ll(const pstEntry_single_ll pL, const pstNode pElm, int k) {
REG_FUNC_NAME("insert_circle_ll");
//const char*_THIS_FUNCTION_ = "insert_circle_ll";
enErr_val enRetVal = ERR_SUCCESS;
if (!pL || !pElm || k < 1) {
fprintf(stderr, "input parameter invalid!\n");
return;
}
.....
}
可以看到,每次对输入参数(入参)或者使用malloc/alloc/calloc等内存分配函数进行内存分配时,安全的做法都是要进行参数合法性检测,如果量少,总是重复这些工作,如果像我在前期一样为了练打字速度的话,OK没问题,但是假以时日,当你手速已经可以跟上想法的时候,就需要考虑“我经常做练习、敲代码,总是做这样重复的事,是不是要提升提升了?”,事实上也是如此,而且这样重复的工作,很不利于在写代码时保持逻辑清晰,代码看起来也非常乱,毫无美感。正是如此,在工作和网络知识相辅相成下,我添加了这样的一段代码,极大的提高了我练习时的效率,也让手指、手腕能舒服一点了,当然,如果你像我一样喜欢敲代码,那么我建议你准备一个机械键盘......言归正传,下面是我新增的代码,只在预处理阶段被展开,不占用程序运行时间,这点,在通信IT领域相信大家都明白其价值:
#define REG_FUNC_NAME(pcName) (_THIS_FUNCTION_ = pcName)
#define DEBUG_FMT "FILEID: %d,LINE: %d,FUNC: %s"
#define DEBUG_INFO FIELDID,__LINE__,_THIS_FUNCTION_
#define RET_LOCAL(enRetVal) do {\
if ((enRetVal) > ERR_ENUM_BUT || (enRetVal) < ERR_FAIL)\
fprintf(stderr, "unknown error:" DEBUG_FMT "\n", DEBUG_INFO); \
else if ((enRetVal) != ERR_SUCCESS)\
fprintf(stderr, "error:" DEBUG_FMT ",%s\n", \
DEBUG_INFO, ErrArr[(enRetVal)].info.pStr);\
return enRetVal; \
} while (0);
我用宏RET_LOCAL(enRetVal)替换了原来的fprintf打印函数,其实就是将fprintf封装在了do{...}while(0)代码块内,同时在可维可测性上极大的提高了,定位手段增加了文件号、行号、函数名,同时将所有的错误都封装在了同一个枚举体内,当然,对变量的自注性也大大提高,每个变量只是看变量名就能知道其变量类型,这样一来,我进行参数校验的时候,只需要使用宏RET_LOCAL(),代码风格编程了如下这样子的,
int insert_circle_ll(const pstEntry_single_ll pL, const pstNode pElm, int k) {
REG_FUNC_NAME("insert_circle_ll");
//const char*_THIS_FUNCTION_ = "insert_circle_ll";
enErr_val enRetVal = ERR_SUCCESS;
if (!pL || !pElm || k < 1) RET_LOCAL(enRetVal = ERR_INVALID_PARAMETER);
if (CIRCLE_LIST_FULL(pL)) RET_LOCAL(enRetVal = ERR_FULL);
pstNode pCur = pL->head;
if (CIRCLE_LIST_EMPTY(pL)) {
if (k > 1) RET_LOCAL(enRetVal = ERR_INVALID_PARAMETER);
pL->head = pElm;
pElm->next = pElm;
}
...
}
顿时清爽干净有没有?好了如果你喜欢这样的风格,那么我们就接着往下看,怎么实现这个宏?我们一一讲解:
#define REG_FUNC_NAME(pcName) (_THIS_FUNCTION_ = pcName)
首先要明确,宏的作用就是文本替换,没有其他作用,所以,_THIS_FUNCTION_ 的作用就是替换 pcName,而从其命名,就可以知道,是一个 char*类型的指针,p前缀表示指针pointer,c表示char,为了使用这个指针,首先我们要定义它,在哪里定义?有以下几点考虑:
基于以上的3点要求,我分开两个文件使用它:
/**************不友好的做法,全局变量被覆盖***************/
//GLOBAL.h
extern const char *pcFuncName;
//A.c
const char *pcFuncName = NULL;
/**************使用局部变量代替***************/
#define REG_FUNC_NAME(pcName) const char*_THIS_FUNCTION_ = pcName
分别放在全局头文件和任意一个c文件中,当然,最好有专门一个c文件存放这类全局变量,毕竟这样好看又舒服。然后在每个函数中,你就可以使用宏REG_FUNC_NAME()去给函数注册函数名,用法如下:
int insert_circle_ll(const pstEntry_single_ll pL, const pstNode pElm, int k) {
REG_FUNC_NAME("insert_circle_ll");
}
#define DEBUG_INFO FIELDID,__LINE__,_THIS_FUNCTION_
行号我直接使用了C所支持的宏 _ _ LINE _ _,注意前后都有下划线,而且这里为了看起来更清晰,把下划线以空格隔开,实际上是没有空格在内的;
文件ID的宏建立与函数名类型,下面直接上代码:
//GLOBAL.h
extern int iFieldId;
//A.c
#define FIELDID (FIELD_ID_START + 0)
//B.c
#define FIELDID (FIELD_ID_START + 1)
...
//Z.c
#define FIELDID (FIELD_ID_START + 25)
...
错误信息枚举把所有的错误类型都包括了进来,这里面有些小技巧,留给喜欢C代码的你发掘,话不多说,直接上代码:
//GLOBAL.h
typedef enum enFuncType {
FUNCTION_TYPE_INVALID = -1,
FUNCTION_TYPE_SINGLE_LINKED_LIST,
....
FUNCTION_TYPE_BUT,
FUNCTION_TYPE_END
}enFuncType;
typedef enum enErr_val {
ERR_INVALID = -1,
ERR_FAIL,
...
ERR_ENUM_BUT,
ERR_ENUM_END = 999
}enErr_val;
typedef struct ERROR_LOCAL {
enErr_val val;
stString info;
}stERROR_LOCAL;
extern stERROR_LOCAL ErrArr[ERR_ENUM_BUT];
//A.c
stfnCircleList fn;
stERROR_LOCAL ErrArr[ERR_ENUM_BUT] = {
{ ERR_FAIL ,{ "fail!" , LEN_INVALID } },
{ ERR_SUCCESS ,{ "success!" , LEN_INVALID } },
...
{ ERR_NO_SPECIFIC_ELEMENT ,{ "no specific element!" , LEN_INVALID } },
};
运行实例如下
C开发维测手段暂时就讲到这里-)