Write your own printf function in C language

Analysis of the first part

First look at the prototype declaration of the printf function:
int __cdecl printf(const char * __restrict__ _Format,...);

The above is the function declaration I copied from Dev-C++. In fact, no matter which version the declaration part has the same function.

Next, let's take a look at what is the return value of printf?


The picture above is a result of my own actual test.

6621! 66 everyone can think of, it is the value I asked to print, what is the 21 thing, take a look at the picture below


The first line outputs 51, and 5 is the number to be output, so what is the return value of printf 1?

Then look at the second line 112, 11 is the number we want to output, what is the return value 2 of printf?

Combining the above two examples, I think everyone has guessed that the return value of printf is the number of characters to be output to the display screen.

Then give another example to prove the above guess:

The first line is the hexadecimal number we want to output, a total of 10 characters (including the prompt composed of 8 numbers and 0x characters)

Then if according to what I just said, the return value of the printf output on the second line should be 10, what's going on?

Look carefully, the last character of the internal string to be output by printf is '\n', which is called an escape character, and its function is a newline. That is, jumping from the first row to the second row, you can also see from the figure that 11 is in the second row. Although escape characters cannot be printed, they are also characters, so they also occupy one byte, so there are 11 characters in total.

Then the problem at the beginning should also be solved!

look again

 __cdecl , __restrict__  (new C99 keywords)

What are these two things, I googled it

__cdecl  is the abbreviation of C Declaration (declaration, declaration), which indicates the default function calling method of C language: all parameters are pushed onto the stack from right to left, and these parameters are cleared by the caller, which is called manual stack clearing.

called function

不会要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。

关键字restrict通过允许编译器优化某几种代码增强了计算支持。它只可用于指针,并表明指针是访问一个数据对象的惟一且初始的方式。(详细使用请百度一下就知道)

具体来说,这两个关键字只是起到修饰作用。没有这连个对程序的运行也不会有什么影响。

现在分析函数的参数:

const char *  _Format, ...

第一个参数为(_Format)字符指针,const 意味着在这个函数里不能修改这个字符串里的内容(常字符串),第二个参数为(...)。

第一个参数字符串,不用多说。。。

第二个参数...,表示参数个数没有限制。

不定参数分析:

说到这里,我们就必须说一下一个标准库必须有的头文件了:

stdarg.h

这个头文件里定义了这么几个重要的宏!

va_list

va_start(ap, param)

va_arg(ap, type) 

va_end(ap)

va_copy(dest, src)

typedef char *va_list;

​#define _AUPBND (sizeof (acpi_native_int) - 1)

​#define _ADNBND (sizeof (acpi_native_int) - 1)

​#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))

​#define va_arg(ap, T) (*((T*) (((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))

​#define va_end(ap) (void) 0

​#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))

以上的一些代码是我在linux源代码里定义的一些宏。

va_list 的是一个字符指针可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。

va_start(ap,v)对ap 进行初始化,用于获取参数列表中可变参数的首指针

 ap用于保存函数参数列表中可变参数的首指针(即,可变参数列表)

   A为函数参数列表中最后一个固定参数

va_arg用于获取当前ap所指的可变参数并将并将ap指针移向下一可变参数  * 输入参数ap(类型为va_list): 可变参数列表,指向当前正要处理的可变参数  * 输入参数T: 正要处理的可变参数的类型  * 返回值: 当前可变参数的值

va_end用于结束对可变参数的处理。

实际上,va_end被定义为空.它只是为实现与va_start配对(实现代码对称和"代码自注释"功能)

当然这里对几个宏的内容就不多做介绍了,后面闲了再对具体做解释,大家先知道他们的作用,等一下我们下面会遇到。

第二部分写函数

接下来说一下为什么要自己写printf函数,为什么不用系统的printf函数

1. 标准库的库函数都不开源,也就是看不到代码,不好调试

2. 标准库的printf比较复杂,代码量提升了,而且速度比较慢,局限性大

第一个原因就不解释了

第二个说一下,标准库的printf只能在pc机上用比如用在没有显示器的东西上就不能用了,特别是在小型嵌入式系统或单片机上,本身ROM就小,而且速度比较慢(几M--几十M的CPU)。特别是大多处情况下浮点型并不需要输出,而标准库的printf浮点运算占了很大一部分,这部分很大程度上降低了CPU对气它程序的计算,所以说,在小型系统中,写一个自己的printf的是至关重要的。

 说了这么多,那就开始开干吧!

 
 
int m_printf(const char *str,...)
{
​     va_list ap;//定义一个可变 参数的(字符指针) 
     ​int val,r_val;
     char count,ch;​
     char *s = NULL;​
     int res = 0;//返回值

     va_start(ap,str);//初始化ap
     while('\0' != *str)//str为字符串,它的最后一个字符肯定是'\0'(字符串的结束符)
     { 
          switch(*str)
          {
              case '%':	//发送参数
              str++;
              ​switch(*str)
              {
                   case 'd': //10进制输出
                        val = va_arg(ap, int); 
 			r_val = val; 
                        count = 0; 
                        while(r_val)​
                        { 
                             count++; //整数的长度
                             r_val /= 10;
                        }​
                        res += count;​//返回值长度增加​ 
                        r_val = val; 
                        while(count)
                        { 
                              ch = r_val / m_pow(10,count - 1);
                              r_val %= m_pw(10,count - 1);​
                              m_putchar(ch + '0');​ //数字到字符的转换 
                              count--;​ 
                        }​ 
                        break;
                  case 'x': //16进制输出 
                        val = va_arg(ap, int); 
                        r_val = val; 
                        count = 0;
                        while(r_val)​ 
                        { 
                             count++; //整数的长度 
                             r_val /= 16; 
                        }​ 
                        res += count;​ //返回值长度增加​ 
                        r_val = val; 
                        while(count) 
                        { 
                              ch = r_val / m_pow(16, count - 1); 
                              r_val %= m_pw(16, count - 1);​ 
                              if(ch <= 9)​
                                  m_putchar(ch + '0');​ 	//数字到字符的转换 
                              else 
                                  m_putchar(ch - 10 + 'a')​; 
                              count--;​ 
                        }​ 
                 break;
                 case: 's': //发送字符串 
                      s = va_arg(a, char *); ​	
                      m_putstr(s);​ //字符串,返回值为字符指针 
                      res += strlen(s);​ //返回值长度增加 ​ 
                 break;​​ 
                 case 'c' 
                      m_putchar( (char)va_arg(ap, int )); //大家猜为什么不写char,而要写int 
                      res += 1;​ 
                 ​break;
                default :;
             }​
             break;
          case '\n':
               ​m_putchar('\n'); 
               res += 1;​
               break;​
          case '\r':
               m_putchar('\r'); 
               res += 1;​
               break;​
​          defaut :  //显示原来的参数的字符串(不是..里的参数o)
               m_putchar(*str)​;
               res += 1;​
          }​
         str++;​
     }
     va_end(ap);
     return res;​
}

 
 
 
  上面用到了一些函数,大概说一下​   pow 就是 x ^y 
  

static unsigned long m_pow(int x,int )
​​{
      ​unsigned long sum = 1;
      while(y--)
      {
           sum *= x;​
      }​
      return sum;​
}​
//打印字符​
void m_putchar(const char ch)
{
       //这部分底层一般由自己实现,下面的这两行是我自己在STM32串口上的实现
      ​while(RESET == USART_GetFlagStatus(USART1,USART_FLAG_TC));
      USART_SendData(USART1,ch);
}

//打印字符串
void m_pustr(const char *str)
{
      while(*str)
      {
            m_putchar(*str++);
      }​
}​
好了,大概就这样,同时也希望大家和我一块学习讨论提高。





Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325942144&siteId=291194637