C语言——设计printf调试宏

前言

借调试宏的设计,梳理下C语言宏的用法

重定向printf打印

嵌入式设备基本会配置RS232串口作为调试IO接口,假设底层串口单字节输出函数为SERIAL_PutChar(),利用fputc()和fputs()重定向printf函数

void fputc(int byte, FILE* stream)
{
    (void)stream;

    SERIAL_PutChar(byte);
}

void fputs(const char *pstr, FILE *stream)
{
    (void)stream;

    while(*pstr)
    {
        SERIAL_PutChar(*pstr++);
    }
}

这样在代码里面利用printf()函数输出的字符串都老老实实从调试串口出来

调试宏使用场景

某个C驱动模块,希望在调试时打印调试信息,而产品代码中不显示调试信息。

v1--单参数宏

#define DRV_DEBUG 1
#if DRV_DEBUG
    #define DRV_PRINT(x) printf(x)
#else
    #define DRV_PRINT(x) 
#endif

这个版本的DRV_PRINT(x)只能输出单变量——纯字符串

void foo()
{
    DRV_PRINT("Driver Initialize Success!");
}

不需要打印调试信息时,更改DRV_DEBUG宏定义

#define DRV_DEBUG 0

当然也可以直接这样定义

#define DRV_PRINT printf

但是如果宏调用了多个参数:

void foo()
{
    DRV_PRINT("Driver Initialize Success: ver %d.%d !", 1, 2);
}

产品代码中的#define DRV_PRINT(x)将编译错误!

怎么办?一种处女座肯定接受不了的做法,多加对括号

void foo()
{
    DRV_PRINT(("Driver Initialize Success: ver %d.%d !", 1, 2));
}

不管是调试代码还是产品代码,编译都OK

v2--指定参数宏

#define DRV_DEBUG 1
#if DRV_DEBUG
    #define DRV_PRINT(fmt, val1, val2) printf(fmt, val1, val2)
#else
    #define DRV_PRINT(fmt, val1, val2) 
#endif

如果只需要打印一个变量,第2个参数用随意值填位,如

void foo()
{
    DRV_PRINT("Driver Initialize Success: ver %d !", val1, 2);
}

类似,如果有4个参数,就:

void foo()
{
    DRV_PRINT("Driver Initialize Success: ver %d !", val1, 2, 3, 4);
}

很傻,但是没办法:(,VxWorks 5.5内核代码里就是这样干的!

v3--参数数量可变宏

C90和C++中可将宏声明为接受可变数量的自变量,如ARM编译器是这样的:

#define DRV_DEBUG 1
#if DRV_DEBUG
    #define DRV_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)
#else
    #define DRV_PRINT(fmt, ...) 
#endif

现在DRV_PRINT用法和printf完全一样了,这么爽的功能,C2000编译器却不支持!

题外话,注意这个特性C90支持,而C90是C++的一个子集,但是C99和C++却不兼容了

分层次LOG输出

有时候,某个模块,有输入跟踪信息,输出信息,错误信息等,如果我想单独打开某部分信息,可以这样设计

#define DRV_DEBUG 1
#define DRV_DEBUG_IN 0x0001
#define DRV_DEBUG_OUT 0x0002
#define DRV_DEBUG_ERR 0x0004
#define DRV_DEBUG_ALL 0xFFFF
#if DRV_DEBUG
        unsigned int drv_flags = DRV_DEBUG_ERR | DRV_DEBUG_OUT;
    #define DRV_PRINT(flag, fmt, ...) \
            do{\
                if(drv_flags & flag){\
                printf(fmt, __VA_ARGS__)}\
            }while(0)
#else
    #define DRV_PRINT(fmt, ...) 
#endif

NOTE: 多行宏,注意换行前加\

这样,我只打印OUT和ERR:

void drv_write(char* msg_out)
{
    DRV_PRINT(DRV_DEBUG_OUT, "Drivr write %s", msg_out); // 输出
        DRV_PRINT(DRV_DEBUG_ERR, "Drivr write %s", msg_out); // 输出
        DRV_PRINT(DRV_DEBUG_IN, "Drivr write %s", msg_out); // 不输出
}

进一步,可以设计针对整个系统不同模块的LOG输出控制!TCP/IP协议栈实现Lwip就是这么干的!

总结

在嵌入式C语言里面,运用printf调试宏,有助于事后分析,定位BUG,多多益善!

你可能感兴趣的:(embedded,c)