嵌入式C语言(七)

简介: 嵌入式C语言(七)

__attribute__扩展的format属性,来指定变参函数的参数格式检查。

__attribute__((format (archetype, string-index, first-to-check)))
void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));

这个作用其实就是自定义打印。因为我们在项目中需要实现一些自定义的调试函数。

用户在调用这些接口函数时参数往往不固定,所以就需要用到这个format属性

现在来解释解释上面的代码:

定义一个LOG()变参函数,用来实现日志打印功能。

编译器在编译程序时,如何检查LOG()函数的参数格式是否正确呢?

方法其实很简单,通过给LOG()函数添加__attribute__((format(printf,1,2)))属性声明就可以了。

这个属性声明告诉编译器:你知道printf()函数不?你怎么对printf()函数进行参数格式检查的,就按照同样的方法,对LOG()函数进行检查。

属性format(printf,1,2)有3个参数:

第1个参数printf是告诉编译器,按照printf()函数的标准来检查;
第2个参数表示在LOG()函数所有的参数列表中格式字符串的位置索引;
第3个参数是告诉编译器要检查的参数的起始位置。

稍微有点米糊糊?整个栗子:

拿第二个展开说说

void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));

在这个LOG()函数中有2个参数:

第1个参数是格式字符串,

第2个参数是要打印的一个常量值0,用来匹配格式字符串中的占位符。

这个格式字符串是什么哇?

一个字符串中含有格式匹配符,那么这个字符串就是格式字符串。 这个%d就是格式匹配符,也叫做占位符。

打印的时候,参数的值会代替这个占位符显示出来。

我们通过format(printf,1,2) 属性声明,告诉编译器:

LOG()函数的参数,其格式字符串的位置在所有参数列表中的索引是1,即第一个参数;

要编译器帮忙检查的参数,在所有的参数列表里索引是2。

知道了LOG()参数列表中格式字符串的位置和要检查的参数位置,编译器就会按照检查printf的格式打印一样,对LOG()函数进行参数检查了。

换个方式定义一下LOG()

void LOG(int num, char *fmt, ...) __attribute__((format(printf,2,3)))

在这个函数定义中,多了一个参数num,格式字符串在参数列表中的位置发生了变化(在所有的参数列表中,索引由1变成了2),要检查的第一个变参的位置也发生了变化(索引从原来的2变成了3),那么我们使用format属性声明时,就要写成format(printf,2,3)的形式了。

…在这里就是指的参数不确定。

其实我刚刚开始看这里很疑惑,那么直接用printf不行吗? 留着,我们先接着下面来了解一下关于变参函数

变参函数的设计与实现

普通的函数,实参与形参一一匹配。

而变参函数,和printf函数一样,参数的个数和类型都是不固定的。

要首先解析实际传进来的实参,保存起来,然后才能像普通函数那样,对实参进行各种操作。

这个用过printf()函数的都知道的,无所不能。

整个栗子瞅瞅:

在上面的函数中,有一个固定的参数count,这个固定参数的存储地址后面,就是一系列参数的地址。

在print_num()函数中,首先获取count参数地址,然后使用&count+1就可以获取下一个参数的地址,使用指针变量args保存这个地址,并依次访问下一个地址,就可以直接打印传进来的各个实参值了。(让我想起了零长数组,其实第一个参数占个位,这很关键,args = &count+1,就是指向了后续的参数地址)

修改一下:

在这个程序中,我们使用char类型的指针。涉及指针运算,一定要注意,因为每一个参数的地址都是4字节大小,所以我们获取下一个参数的地址是(char)&count+4;(指针的大小记住是固定的4字节,这也是为什么文件传递的时候尽量传递指针,这样可以提高效率。)

不同类型的指针加1操作,转换为实际的数值运算是不一样的。对于一个指向int类型的指针变量p,p+1表示p+1sizeof(int)。对于一个指向char类型的指针变量,p+1表示p+1sizeof(char)因此这里整了4.

再变变?

对于变参函数,编译器或操作系统一般会提供一些宏给程序员使用,用来解析函数的参数列表,编译器提供的宏有以下3种。

● va_list:定义在编译器头文件stdarg.h中,如typedef char*va_list;。

● va_start(fmt,args):根据参数args的地址,获取args后面参数的地址,并保存在fmt指针变量中。

● va_end(args):释放args指针,将其赋值为NULL。

变参函数V4.0

上面使用编译器提供的三个宏,这样可以让我们不用自己去解析参数。

现在打印的功能也不自己实现了。

使用vprintf()函数完成打印功能。vprintf()函数的声明在stdio.h头文件中。

vprintf()函数有两个参数:一个是格式字符串指针,一个是变参列表。

接下来,我们需要对其添加format属性声明,让编译器在编译时,像检查printf()一样,检查myprintf()函数的参数格式。

我就纳闷了,为啥有现成的还要自己去实现?

就是想实现自己的日志打印函数,原因其实很简单,自己实现的打印函数,**除了可以实现自己需要的打印格式,**还有很多优点,可以实现打印开关控制和优先级控制还可以根据需要不断添加功能。

你在调试的模块或系统中,可能有好多文件。如果在每个文件里都添加printf()函数打印,调试完成后再删掉,是不是很麻烦?而使用我们自己实现的打印函数,通过一个宏开关,就可以直接关掉或打开,维护起来更加方便,如下面的代码。

当我们在程序中定义一个DEBUG开关宏时,LOG()函数实现正常的打印功能;当我们删掉这个DEBUG宏时,LOG()函数就是一个空函数。通过这个宏,我们实现了打印函数的开关功能。在Linux内核的各个模块或子系统中,你会经常看到各种自定义的打印函数或宏,如pr_debug、pr_info、pr_err等。

顿时这个意义就来了,豁然舒服了。

除此之外,你还可以通过宏来设置一些打印等级。如可以分为ERROR、WARNNING、INFO等打印等级,根据设置的打印等级,模块打印的日志信息也不一样。

牛啊牛啊

在上面的程序中,我们封装了3个打印函数:INFO()、WARN()和ERR(),分别打印不同优先级的日志信息。在实际调试中,我们可以根据自己需要的打印信息,设置合适的打印等级,就可以分级控制这些打印信息了。

  • 资料:《嵌入式C语言自我修养——从芯片、编译器到操作系统》
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
相关文章
|
6天前
|
存储 编解码 编译器
嵌入式C语言(四)
嵌入式C语言(四)
30 0
|
6天前
|
编译器 Linux C语言
嵌入式C语言(八)
嵌入式C语言(八)
21 0
|
6天前
|
存储 编译器 C语言
嵌入式C语言(六)
嵌入式C语言(六)
26 0
|
6天前
|
编译器 C语言 芯片
嵌入式C语言(九)
嵌入式C语言(九)
19 0
|
6天前
|
缓存 小程序 编译器
嵌入式C语言(十)
嵌入式C语言(十)
26 0
|
6天前
|
安全 算法 开发工具
【C 言专栏】基于 C 语言的嵌入式系统开发
【5月更文挑战第1天】本文探讨了C语言在嵌入式系统开发中的核心作用。嵌入式系统作为专用计算机系统广泛应用于家电、汽车、医疗等领域,具备实时性、低功耗等特点。C语言因其高效性、可移植性和灵活性成为开发首选。文章介绍了开发流程,包括需求分析、硬件选型、软件设计至部署维护,并强调中断处理、内存管理等关键技术。C语言在智能家居、汽车电子和医疗设备等领域的应用实例展示了其广泛影响力。面对硬件限制、实时性要求和安全挑战,开发者需不断优化和适应新技术趋势,以推动嵌入式系统创新发展。
【C 言专栏】基于 C 语言的嵌入式系统开发
|
6天前
|
传感器 算法 C语言
C语言在嵌入式系统开发中的优化策略与代码实现
C语言在嵌入式系统开发中的优化策略与代码实现
29 1
|
6天前
|
存储 编译器 Linux
嵌入式C语言(五)
嵌入式C语言(五)
17 0
|
6天前
|
Linux API C语言
lua 如何在嵌入式Linux中与c语言结合
lua 如何在嵌入式Linux中与c语言结合
12 1
|
算法 Linux Android开发
本CSDN博主将与北京航天航空大学出版社合作出版<嵌入式C语言技术实战开发>一书
本书作者由以下成员合作编写:     杨源鑫,主编,毕业于广州科技贸易职业学院电子应用技术专业,在校期间一并考取了华南理工大学本科数字媒体艺术专业。2015年7月工作至今,任伟易达集团嵌入式系统工程师一职,主要从事单片机,linux,Android底层开发等相关的技术。
2928 0