嵌入式C语言自我修养08:变参函数的格式检查

  

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就可以获取下一个参数的指针地址,使用指针变量参数保存这个地址,并依次访问下一个地址,就可以直接打印传进来的各个实参值了。程序运行结果如下。

嵌入式C语言自我修养08:变参函数的格式检查