# 工欲善其事必先利其器-C语言拓展--嵌入式C语言(七)

工欲善其事必先利其器-C语言拓展–嵌入式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(),分别打印不同优先级的日志信息。在实际调试中,我们可以根据自己需要的打印信息,设置合适的打印等级,就可以分级控制这些打印信息了。

酷的 bro

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
宋宝华嵌入式 C/C++语言精华文章集锦 C/C+语言 struct 深层探索 ............................................................................2 C++中 extern "C"含义深层探索........................................................................7 C 语言高效编程的几招...............................................................................11 想成为嵌入式程序员应知道的 0x10 个基本问题 .........................................................15 C 语言嵌入式系统编程修炼...........................................................................22 C 语言嵌入式系统编程修炼之一:背景篇 ............................................................22 C 语言嵌入式系统编程修炼之二:软件架构篇 ........................................................24 C 语言嵌入式系统编程修炼之三:内存操作 ..........................................................30 C 语言嵌入式系统编程修炼之四:屏幕操作 ..........................................................36 C 语言嵌入式系统编程修炼之五:键盘操作 ..........................................................43 C 语言嵌入式系统编程修炼之六:性能优化 ..........................................................46 C/C++语言 void 及 void 指针深层探索 .................................................................50 C/C++语言可变参数表深层探索 .......................................................................54 C/C++数组名与指针区别深层探索 .....................................................................60 C/C++程序员应聘常见面试题深入剖析(1) ..............................................................62 C/C++程序员应聘常见面试题深入剖析(2) ..............................................................67 一道著名外企面试题的抽丝剥茧 ......................................................................74 C/C++结构体的一个高级特性――指定成员的位数 .......................................................78 C/C++中的近指令、远指针和巨指针 ...................................................................80 从两道经典试题谈 C/C++中联合体(union)的使用 ......................................................81 基于 ARM嵌入式 Linux 移植真实体验 ................................................................83 基于 ARM嵌入式 Linux 移植真实体验(1)――基本概念 ...........................................83 基于 ARM嵌入式 Linux 移植真实体验(2)――BootLoader .........................................96 基于 ARM嵌入式 Linux 移植真实体验(3)――操作系统 ..........................................111 基于 ARM嵌入式 Linux 移植真实体验(4)――设备驱动 ..........................................120 基于 ARM嵌入式 Linux 移植真实体验(5)――应用实例 ..........................................135 深入浅出 Linux 设备驱动编程 .......................................................................144 1.Linux 内核模块..............................................................................144 2.字符设备驱动程序 ...........................................................................146 3.设备驱动中的并发控制 .......................................................................151 4.设备的阻塞与非阻塞操作 .....................................................................157
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TrustZone_Hcoco

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值