printf函数实现

本文介绍了在嵌入式系统中实现串口格式化输出的三种方法,包括使用printf、sprintf和自定义printf函数。详细讲解了如何实现自定义printf函数,涉及到变量参数列表的处理,包括对字符串、字符、整数等类型的支持,并提供了相关的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

序: 

     在嵌入式系统中,串口常用来辅助调试输出一些调试信息。所以有必要实现串口的格式化输出功能,这可以由3种方法实现(就我目所知).

1)使用系统库函数printf(),这就需要重载输入,输出函数int fputc(int ch, FILE *f);int fgetc(FILE *f).

2)使用sprintf()函数将数据格式化到数组,然后将数组输出.也可以使用vsprintf().

3)自己写类似printf()函数.

 

这里假设已经编写了基本的输入输出函数如下:
int  sendchar(int ch);  // 发送字符
int  sendstr(char *str);//发送字符窜
int  getkey(void);      // 接受字符

 

1)第一种方法的实现
比较简单只要实现以下2个函数:

int fputc(int ch, FILE *f) {
  return (sendchar(ch));
}

int fgetc(FILE *f) {
  return (getkey());
}

使用方法:
#include <string.h>
void main()
{
 ...
 printf("%s,%d,%x","hello",0x10,0x10);//输出: hello,16,10
 ...
}

2)第二种方法的实现
void Uart_Printf(const char *fmt,...)
{
    va_list ap;
    char string[256];
    va_start(ap,fmt);
    vsprintf(string,fmt,ap);
    Uart_SendString(string);
    va_end(ap);
}

3)第三种方法的实现

int myprintf (const char* str,...)
{
 va_list arp; 
 uint8 c, f, r;
 ULONG val;
 char s[16];
 int i, w, res, cc;

 va_start(arp, str);

 for (cc = res = 0; cc != EOF; res += cc) {
  c = *str++;
  if (c == 0) break;   /* End of string */
  if (c != '%') {    /* Non escape cahracter */
   sendchar(c);
   cc=1;    //cc保存当前循环发送的数据
   continue;
  }
  w = f = 0;    //f为格式 ,w为宽度
  c = *str++;
  if (c == '0') {    /* Flag: '0' padding */
   f = 1;
   c = *str++;    //等于c = *(str++);先取值,最后str++ ;f的第0位代表用0填充对齐 c = *(str++); 

  }
  while (c >= '0' && c <= '9') { /* Precision */
   w = w * 10 + (c - '0'); //计算对齐宽度
   c = *str++;
  }
  if (c == 'l' || c=='L') {    /* Prefix: Size is long int */
   f |= 2; c = *str++;    //f的第二位代表Long前缀
  }
  if (c == 's' || c == 'S') {    /* Type is string */
   cc = sendstr(va_arg(arp, char*));//发送字符窜,cc=放松字符个数
   continue;
  }
  if (c == 'c' || c == 'C') {    /* Type is character */
   sendchar(va_arg(arp, int)); //char 改 int
   cc = 1;
   continue;
  }
  r = 0;
  if (c == 'd' || c =='D') r = 10;   //r代表进制  /* Type is signed decimal */
  if (c == 'u' || c == 'U') r = 10;  /* Type is unsigned decimal */
  if (c == 'X' || c == 'x') r = 16;  /* Type is unsigned hexdecimal */
  if (r == 0) break;   /* Unknown type */
  if (f & 2) {    /* Get the value */
   val = (ULONG)va_arg(arp, long); //获取Long型参数,arp指向下一个参数

  } else {  //没有'l'标志
   val = (c == 'd') ? (ULONG)(long)va_arg(arp, int) : (ULONG)va_arg(arp, unsigned int);
   //不管是int 还是 unsigned int 都转化为 unsigned long
  }
  /* Put numeral string */
  if (c == 'd') {//如果是'd'前缀就要判断刚获取的参数是正还是付
   if (val >= 0x80000000) { //最高位为1说明是负的
    val = 0 - val;
    f |= 4;//f的第三位代表"+/-"
   }
  }
  i = sizeof(s) - 1; s[i] = 0;//i=15
  do {
   c = (uint8)(val % r + '0');//r代表进制
   if (c > '9') c += 7; //对于16进制 转换到对应的'abc...'需要+7 :'/58'+7='65'='a'
   s[--i] = c; //从后面开始保存
   val /= r;
  } while (i && val);
  if (i && (f & 4)) s[--i] = '-';//是负数添加'-'号
  w = sizeof(s) - 1 - w;
  while (i && i > w) s[--i] = (f & 1) ? '0' : ' ';
  cc = sendstr(&s[i]);
 }
 va_end(arp);
 return (cc == EOF) ? cc : res;
}


变参数表的调用形式以及原理:
参数在内存中存放格式:大多数情况堆栈是从高到低生长,函数参数入栈时后面的参数先入栈。参数弹出顺序与入栈顺序相反。


举个例子如下:
void func(int x, float y, char z);
那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。

 

下面是MDK <stdarg.h> 里面重要的几个宏定义如下:
  typedef struct __va_list { void *__ap; } va_list;
   /*
    * an array type suitable for holding information needed by the macro va_arg
    * and the function va_end. The called function shall declare a variable
    * (referred to as ap) having type va_list. The variable ap may be passed as
    * an argument to another function.
    * Note: va_list is an array type so that when an object of that type
    * is passed as an argument it gets passed by reference.
    */
  #define va_arg(ap, type) __va_arg(ap, type)
   /*
    * The va_arg macro expands to an expression that has the type and value of
    * the next argument in the call. The parameter ap shall be the same as the
    * va_list ap initialised by va_start. Each invocation of va_arg modifies
    * ap so that successive arguments are returned in turn. The parameter
    * 'type' is a type name such that the type of a pointer to an object that
    * has the specified type can be obtained simply by postfixing a * to
    * 'type'. If type is a narrow type, an error is given in strict ANSI
    * mode, or a warning otherwise. If the type is an array or function type,
    * an error is given.
    * In non-strict ANSI mode, 'type' is allowed to be any expression.
    * Returns: The first invocation of the va_arg macro after that of the
    *          va_start macro returns the value of the argument after that
    *          specified by parmN. Successive invocations return the values of
    *          the remaining arguments in succession.
    *          The result is cast to 'type', even if 'type' is narrow.
    */
#define va_end(ap) ((void)(ap))
   /*
    * The va_end macro facilitates a normal return from the function whose
    * variable argument list was referenced by the expansion of va_start that
    * initialised the va_list ap. If the va_end macro is not invoked before
    * the return, the behaviour is undefined.
    * Returns: no value.
    */

使用方法:
<Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
<Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
<Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
<Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。

 

va_list的"等价"替换:
va_list arp; <==> {char *arp = 0;}
va_start(arp, str);<==>{ arp = (char *)&str; arp += sizeof(str); }
sendstr(va_arg(arp, type));<==> { sendstr( *(type*)arp); arp += sizeof(type);}
va_end(arp); <==>{  arp = 0;}

 

因此上述函数可以改为:

int myprintf ( const char* str, ...)
{
 //va_list arp;
 char *arp = 0;
 uint8 c, f, r;
 ULONG val;
 char s[16];
 int i, w, res, cc;

 //va_start(arp, str);
 arp = (char *)&str;
 arp += sizeof(str);

 for (cc = res = 0; cc != EOF; res += cc) {
  c = *str++;
  if (c == 0) break;   /* End of string */
  if (c != '%') {    /* Non escape cahracter */
   sendchar(c);
   cc=1;    //cc保存当前循环发送的数据
   continue;
  }
  w = f = 0;    //f为格式 ,w为宽度
  c = *str++;
  if (c == '0') {    /* Flag: '0' padding */
   f = 1;
   c = *str++;    //等于c = *(str++);先取值,最后str++ ;f的第0位代表用0填充对齐 c = *(str++); 
  }
  while (c >= '0' && c <= '9') { /* Precision */
   w = w * 10 + (c - '0'); //计算对齐宽度
   c = *str++;
  }
  if (c == 'l' || c=='L') {    /* Prefix: Size is long int */
   f |= 2; c = *str++;    //f的第二位代表Long前缀
  }
  if (c == 's' || c == 'S') {    /* Type is string */
   //cc = sendstr(va_arg(arp, char*));//发送字符窜,cc=放松字符个数
   sendstr( *(char **)arp);
   arp += sizeof(char *);
   continue;
  }
  if (c == 'c' || c == 'C') {    /* Type is character */
      // sendchar(va_arg(arp, int)); //char 改 int
     sendchar(*((int *)arp));
     arp += sizeof(int);
   cc = 1;
   continue;
  }
  r = 0;
  if (c == 'd' || c =='D') r = 10;   //r代表进制  /* Type is signed decimal */
  if (c == 'u' || c == 'U') r = 10;  /* Type is unsigned decimal */
  if (c == 'X' || c == 'x') r = 16;  /* Type is unsigned hexdecimal */
  if (r == 0) break;   /* Unknown type */
  if (f & 2) {    /* Get the value */
   //val = (ULONG)va_arg(arp, long); //获取Long型参数,arp指向下一个参数
   val=(ULONG)(*((long *)arp));
   arp += sizeof(ULONG);

  } else {  //没有'l'标志
   val = (c == 'd') ? (ULONG)(long)va_arg(arp, int) : (ULONG)va_arg(arp, unsigned int);
   //不管是int 还是 unsigned int 都转化为 unsigned long
  /* if(c == 'd')
   {
      val = (ULONG) (*((int*)arp));
     arp += sizeof(int);
   }
   else
   {
    val = (ULONG)(*((unsigned int*)arp));
    arp += sizeof(unsigned int);
   }  */
  }
  /* Put numeral string */
  if (c == 'd') {//如果是'd'前缀就要判断刚获取的参数是正还是付
   if (val >= 0x80000000) { //最高位为1说明是负的
    val = 0 - val;
    f |= 4;//f的第三位代表"+/-"
   }
  }
  i = sizeof(s) - 1; s[i] = 0;//i=15
  do {
   c = (uint8)(val % r + '0');//r代表进制
   if (c > '9') c += 7; //对于16进制 转换到对应的'abc...'需要+7 :'/58'+7='65'='a'
   s[--i] = c; //从后面开始保存
   val /= r;
  } while (i && val);
  if (i && (f & 4)) s[--i] = '-';//是负数添加'-'号
  w = sizeof(s) - 1 - w;
  while (i && i > w) s[--i] = (f & 1) ? '0' : ' ';
  cc = sendstr(&s[i]);
 }
 //va_end(arp);
           arp = 0;
 return (cc == EOF) ? cc : res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值