[原创] st F7 +老人紧急呼救+实现printf 可变参数

247153481   2015-10-11 11:25 楼主


首先列举几个宏  __VA_ARGS__ , va_start , va_arg , va_end
__VA_ARGS__  c语言高级宏,描述可变参数


va_list 用来保存宏va_start、va_arg和va_end所需信息的一种类型。为了访问变长参数列表中的参数,必须声明 va_list类型的一个对象      
定义: typedef char *  va_list;


va_start 访问变长参数列表中的参数之前使用的宏,它初始化用va_list声明的对象,初始化结果供宏va_arg和va_end使用;实质上取得可变参数首地址


va_arg 展开成一个表达式的宏,该表达式具有变长参数列表中下一个参数的值和类型。每次调用va_arg都会修改
              用va_list声明的对象,从而使该对象指向参数列表中的下一个参数;即获得可变参数


va_end  该宏使程序能够从变长参数列表用宏va_start引用的函数中正常返回。表示获取参数结束





问题提出

昨天我们在讨论使用printf重定向到串口的时候,只需要重定义fputc函数即可,并勾选使用microlib 库,但是如果想要自己实现呢,该如何去做

在写代码的时候,印象中c语言的参数是固定的,没有重载,参数不可变


但是在使用printf的时候却可以输入几乎任意个参数,是怎么做到的


其中printf函数原型  int printf( const char* format, ...);


在自己实现printf之前首先介绍一下c语言的可变参数。




可变参数

c 语言参数传递
c语言中,参数传递通过栈来实现,正常情况下的入栈规则为__stdcall ,即参数从右到左,右边的参数先入栈,左边的参数后入栈,结果就是右边的参数在栈底,左边的参数在栈顶。
对于固定参数而言,应该是这样
void func(Int a,int b,int c)
{
...
}
参数排列,(假设向上增长)
a
b
c
对于一般的32位系统而言,栈字节大小应该是4字节
所以这里也就会牵扯到后面的一个4字节对其的问题


这里我们明确知道有几个参数,那么也就知道放在什么地方的是哪个参数,如果是可变参数呢,自然就没有那么顺利了。
传统c语言中允许不带任何固定参数的可变参数函数,真是xxxxx,估计找到参数在个地址都得费劲。还好现在的c要求至少有一个固定参数
,这至少告诉我们参数起始地址。


但编译器怎么知道你到底传入了几个参数?答案是不知道,因此这也给程序的健壮性带来了隐患,可能一不小心就改变其他的内容。

像printf这样,他是根据前面的固定参数来得知有几个参数,经常我们使用的标准输出格式 %c d f g...................

如果你参数个数不匹配,就容易出问题

可变参数的栈结构应该如图所示

高地址|-----------------------------|
|函数返回地址 |
|-----------------------------|
|....... |
|-----------------------------|
|第n个参数(第一个可变参数) |
|-----------------------------|<--va_start后ap指向
|第n-1个参数(最后一个固定参数)|
低地址|-----------------------------|<-- &v



展开上面一开始提到的宏
vs平台下
#define _ADDRESSOF(v)   ( &(v) )
#define _INTSIZEOF(n)   ( (sizeof(&n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v)  ( ap = (char *)( &v ) + _INTSIZEOF(v) )

#define va_arg(ap,t)    ( *(t *)(    (ap += _INTSIZEOF(t)  )   - _INTSIZEOF(t)   ) )

#define va_end(ap)      ( ap = (char *)0 )



解释一下 几个宏的意思
_INTSIZEOF 这个就是为了4字节对齐, 如果n是1,2,3个字节,_INTSIZEOF (n)=4;如果是5,6,7则结果为8


现在来小试一下可变参数


  1. <blockquote>void func(int a, ...)



现在回到正题
如何实现printf

可以这样设想,按照要求的格式打印到一个字符数组里面,然后使用串口打印出整个字符串不就可以了吗?

这用这些宏,我用不同的方法来实现
1、借助于sprintf
  1. #define myprint(...) sprintf(buf,__VA_ARGS__) ;printf("%s",buf);
这样就可以直接使用myprintf了,用法跟printf一样,(ps,这里我是在win 模拟,如果在板子上,printf改成串口发送的就可以了)


2、上面那个太简单了,复杂一点,借助 于  int vsprintf(char *string, char *format, va_list param);
va_list 可变参数地址
将参数全部打印好,然后通过串口发送
  1. int func1(const char *format, ...)
  2. {
3、如果想要自己一句话一句话来实现呢,当然可以,不过那些%XXXXX 格式很麻烦
我在百度上找了一个 地址在这
  1. #include <stdio.h>
  2. #include <stdarg.h>
  3. #include "print.h"

  4. int main(void)
  5. {
  6.     print("print: %c\n", 'c');
  7.     print("print %d\n", 1234567);
  8.     print("print: %f\n", 1234567.1234567);
  9.     print("print: %s\n", "string test");
  10.     print("print: %b\n", 0x12345ff);
  11.     print("print: %x\n", 0xabcdef);
  12.     print("print: %%\n");
  13.     return 0;
  14. }

  15. void    print(char* fmt, ...)
  16. {
  17.     double vargflt = 0;
  18.     int  vargint = 0;
  19.     char* vargpch = NULL;
  20.     char vargch = 0;
  21.     char* pfmt = NULL;
  22.     va_list vp;

  23.     va_start(vp, fmt);
  24.     pfmt = fmt;

  25.     while(*pfmt)
  26.     {
  27.         if(*pfmt == '%')
  28.         {
  29.             switch(*(++pfmt))
  30.             {
  31.                
  32.                 case 'c':
  33.                     vargch = va_arg(vp, int);
  34.                     /*    va_arg(ap, type), if type is narrow type (char, short, float) an error is given in strict ANSI
  35.                         mode, or a warning otherwise.In non-strict ANSI mode, 'type' is allowed to be any expression. */
  36.                     printch(vargch);
  37.                     break;
  38.                 case 'd':
  39.                 case 'i':
  40.                     vargint = va_arg(vp, int);
  41.                     printdec(vargint);
  42.                     break;
  43.                 case 'f':
  44.                     vargflt = va_arg(vp, double);
  45.                     /*    va_arg(ap, type), if type is narrow type (char, short, float) an error is given in strict ANSI
  46.                         mode, or a warning otherwise.In non-strict ANSI mode, 'type' is allowed to be any expression. */
  47.                     printflt(vargflt);
  48.                     break;
  49.                 case 's':
  50.                     vargpch = va_arg(vp, char*);
  51.                     printstr(vargpch);
  52.                     break;
  53.                 case 'b':
  54.                 case 'B':
  55.                     vargint = va_arg(vp, int);
  56.                     printbin(vargint);
  57.                     break;
  58.                 case 'x':
  59.                 case 'X':
  60.                     vargint = va_arg(vp, int);
  61.                     printhex(vargint);
  62.                     break;
  63.                 case '%':
  64.                     printch('%');
  65.                     break;
  66.                 default:
  67.                     break;
  68.             }
  69.             pfmt++;
  70.         }
  71.         else
  72.         {
  73.             printch(*pfmt++);
  74.         }
  75.     }
  76.     va_end(vp);
  77. }

  78. void    printch(char ch)
  79. {
  80.     console_print(ch);
  81. }

  82. void    printdec(int dec)
  83. {
  84.     if(dec==0)
  85.     {
  86.         return;
  87.     }
  88.     printdec(dec/10);
  89.     printch( (char)(dec%10 + '0'));
  90. }

  91. void    printflt(double flt)
  92. {
  93.     int icnt = 0;
  94.     int tmpint = 0;
  95.    
  96.     tmpint = (int)flt;
  97.     printdec(tmpint);
  98.     printch('.');
  99.     flt = flt - tmpint;
  100.     tmpint = (int)(flt * 1000000);
  101.     printdec(tmpint);
  102. }

  103. void    printstr(char* str)
  104. {
  105.     while(*str)
  106.     {
  107.         printch(*str++);
  108.     }
  109. }

  110. void    printbin(int bin)
  111. {
  112.     if(bin == 0)
  113.     {
  114.         printstr("0b");
  115.         return;
  116.     }
  117.     printbin(bin/2);
  118.     printch( (char)(bin%2 + '0'));
  119. }

  120. void    printhex(int hex)
  121. {
  122.     if(hex==0)
  123.     {
  124.         printstr("0x");
  125.         return;
  126.     }
  127.     printhex(hex/16);
  128.     if(hex < 10)
  129.     {
  130.         printch((char)(hex%16 + '0'));
  131.     }
  132.     else
  133.     {
  134.         printch((char)(hex%16 - 10 + 'a' ));
  135.     }
  136. }



关于可变参数的一些推理证明
网上copy的
对于两个正整数 x, n 总存在整数 q, r 使得
x = nq + r, 其中 0<= r //最小非负剩余
q, r 是唯一确定的。q = [x/n], r = x - n[x/n]. 这个是带余除法的一个简单形式。在 c 语言中, q, r 容易计算出来: q = x/n, r = x % n.
所谓把 x 按 n 对齐指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 这也相当于把 x 表示为:
x = nq + r', 其中 -n < r' <=0 //最大非正剩余
nq 是我们所求。关键是如何用 c 语言计算它。由于我们能处理标准的带余除法,所以可以把这个式子转换成一个标准的带余除法,然后加以处理:
x+n = qn + (n+r'),其中 0 //最大正剩余
x+n-1 = qn + (n+r'-1), 其中 0<= n+r'-1 //最小非负剩余
所以 qn = [(x+n-1)/n]n. 用 c 语言计算就是:
((x+n-1)/n)*n
若 n 是 2 的方幂, 比如 2^m,则除为右移 m 位,乘为左移 m 位。所以把 x+n-1 的最低 m 个二进制位清 0就可以了。得到:
(x+n-1) & (~(n-1))


最后再加上一个关于调试用的技巧


特别是对于大型程序中,日志是非常重要的,不然问题找都找不到

那么可以利用这几个宏
__FILE__, __LINE__ ,__FUNCTION__


当前文件 当前行 当前函数


可以定义一个这样的宏
  • #define myprintf(...) printf("[lch]:File:%s, Line:%d, Function:%s," \  
  •      __VA_ARGS__, __FILE__, __LINE__ ,__FUNCTION__);  



打印出出错位置,至于打印到哪里,那只是一个流向的问题,流向文件或终端




回复评论 (8)

少年 @574433742 欣欣 @辛昕
能不能介绍一下写文章的技巧
怎么写的看起来更舒服
点赞  2015-10-11 11:29
引用: 247153481 发表于 2015-10-11 11:29
少年 @574433742 欣欣 @辛昕
能不能介绍一下写文章的技巧
怎么写的看起来更舒服

啊?你看我的觉得舒服么?
多写多练这才是真的.....
看看别人为啥不爽,写多了,人家就会告诉你哪里不爽,然后就知道了~~
强者为尊,弱者,死无葬身之地
点赞  2015-10-11 13:31
引用: 辛昕 发表于 2015-10-11 13:31
啊?你看我的觉得舒服么?
多写多练这才是真的.....
看看别人为啥不爽,写多了,人家就会告诉你哪里不 ...

觉得你写的都挺好
点赞  2015-10-11 14:31
引用: 247153481 发表于 2015-10-11 12:31
觉得你写的都挺好

   原来你喜欢xinxin的风格呀,哇咔咔,,,多跟xinxin学习。。。。。
分享铸就美好未来。。。
点赞  2015-10-11 17:43
写的不错,学习了。
点赞  2015-10-11 17:51
引用: 574433742 发表于 2015-10-11 17:43
原来你喜欢xinxin的风格呀,哇咔咔,,,多跟xinxin学习。。。。。

少年你写的也很不错啊,ee的版主都是非常优秀的
点赞  2015-10-11 18:03
引用: arthasarthas 发表于 2015-10-11 17:51
写的不错,学习了。

谢谢
点赞  2015-10-11 18:03

感谢分享
点赞  2015-10-12 19:07
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复