8.1属性声明:格式
GNU通过属性扩展的格式属性,用来指定变参函数的参数格式检查。
它的使用方法如下:
<代码> __attribute__((格式(原型,string-index,首先要检查))) 空白日志(const char * fmt,…)使用__attribute__((格式(printf, 1, 2))), 代码>
我们经常实现一些自己的打印调试函数。这些打印函数往往是变参函数,那编译器编译程序时,怎么知道我们的参数格式对不对呢?因为我们实现的是变参函数,参数的个数和格式都不确定。所以编译器表示压力很大,不知道该如何处理。
办法总是有这的。不,属性的格式属性这时候就自带BGM,隆重出场了。如上面的示例代码,我们定义一个日志变参函数,用来实现打印功能。那编译器编译程序时,如何检查我们参数的格式是否正确呢?其实很简单,通过给日志函数添加属性((格式(printf, 1, 2)))这个属性声明,就是告诉编译器:你知道printf函数不?你怎么对这个函数参数格式检查的,就按同样的方法,对日志函数进行检查。
属性格式(printf, 1, 2)有三个参数。第一个参数printf是告诉编译器,按照printf函数的检查标准来检查,第2个参数表示在日志函数所有的参数列表中,格式字符串的位置索引;第3个参数是告诉编译器要检查的参数的起始位置。是不是没看明白?举个例子大家就明白了。
<代码>日志(“我litao \ n”); 日志(“我litao,有% d房子! \ n ", 0); 日志(“我litao,有% d房子!% d汽车\ n”, 0, 0), 代码>
上面代码,是我们的日志函数使用示例。变参函数,其参数个数跟printf函数一样,是不固定的。那么编译器如何检查我们的打印格式是否正确呢?很简单,我们只需要将格式字符串的位置告诉编译器就可以了,比如在第2行代码中:
<代码>日志(“我litao,有% d房子! \ n ", 0); 代码>
在这个日志函数中有2个参数,第一个是格式字符串,第2个是要打印的一个常量值0,用来匹配格式字符串中的格式符。
什么是格式字符串呢?顾名思义,如果一个字符串中含有格式符,那这个字符串就是格式字符串。比如这个格式字符串:“我litao,我有% d房子! \ n",里面含有格式符%,我们也可以叫它占位符。打印的时候,后面变参的值会代替这个占位符,在屏幕上显示出来。
我们通过格式(printf, 1, 2)属性声明,告诉编译器:日志函数的参数,格式字符串的位置在所有参数列表中的索引是1,即第一个参数,要编译器帮忙检查的参数,在所有的参数列表里索引是2。知道了日志参数列表中格式字符串的位置和要检查的参数位置,编译器就会按照检查printf的格式打印一样,对日志函数进行参数检查。
如果我们的日志函数定义为下面形式:
<代码>空白日志(int num, char * fmt,…)使用__attribute__((格式(printf、2、3))), 代码>
在这个函数定义中,多了一个参数num,格式字符串在参数列表中的位置发生了变化(在所有的参数列表中,索引为2),要检查的第一个变参的位置也发生了变化(索引为3),那我们使用格式属性声明时,就要写成格式(printf、2、3)的形式了。
以上就属是格式性的使用方法,鉴于很多同学,可能对变参函数研究得不多,接下来我们就一起研究下变参函数的设计与实现,加深对本节知识的理解。
8.2变参函数的设计与实现
对于一个普通函数,我们在函数实现中,不用关心实参,只需要在函数体内对形参直接引用即可。当函数调用时,传递的实参和形参个数和格式是匹配的。
变参函数,顾名思义,跟printf函数一样:参数的个数、类型都不固定。我们在函数体内因为预先不知道传进来的参数类型和个数,所以实现起来会稍微麻烦一点。首先要解析传进来的实参,保存起来,然后才能接着像普通函数一样,对实参进行处理。
变参函数初体验
我们接下来,就定义一个变参函数,实现的功能很简单,即打印传进来的实参值。
<代码>空白print_num (int数,…) { int * args; args=,计数+ 1; for (int i=0;我& lt;计数;我+ +) { printf (" * args: % d \ n”, * args); args + +; } } int主要(空白) { print_num (5、1、2、3、4、5); 返回0; }代码>
变参函数的参数存储其实跟主函数的参数存储很像,由一个连续的参数列表组成,列表里存放的是每个参数的地址。在上面的函数中,有一个固定的参数计数,这个固定参数的存储地址后面,就是一系列参数的指针。在print_num函数中,先获取数参数地址,然后使用,计数+ 1就可以获取下一个参数的指针地址,使用指针变量参数保存这个地址,并依次访问下一个地址,就可以直接打印传进来的各个实参值了。程序运行结果如下。