本文参考:https://www.pianshen.com/article/35981882012/
前提的头文件:
//==================================================================================================
typedef char* va_list;
#define _INTSIZEOF1(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF1(v) )
//ap指向fmt后面的地址
//#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_arg(ap,t) (*(t *)( ap=ap + _INTSIZEOF1(t), ap- _INTSIZEOF1(t)))
#define va_end(ap) ( ap = (va_list)0 ) //这里就不解释了,不难理解
unsigned char hex_tab[] = { '0','1','2','3','4','5','6','7',
'8','9','a','b','c','d','e','f' };
//==================================================================================================
一、可变参数的函数是怎么传参的?
我们定义:
int printf0(const char* fmt, ...)
{
va_list ap;
va_start(ap,fmt)
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}
其中比较难理解的就是
va_start(ap,fmt) va_arg(ap,t)
这两个函数是什么意思,原文也解释的不太清楚
研究了一下,可变参数的函数可以看成是一串保持参数地址的字符串,占用一片连续的内存,printf可以看成是类似char**,如下图所示:
这个图很好地说明了问题,fmt的地址,参数a的地址,参数A的地址(图里的0x0098f914这列地址)是连续的,我们用ap指向了这些地址:
( ap = (va_list)&v + _INTSIZEOF1(v) )
也就是说ap的内容为0x0098f918,指向了第一个参数a。
假设传入一串参数:
printf0("test=%c,%c,%cnr", 'a', 'A','F');
那么fmt地址指向字符串
test=%c,%c,%cnr
ap指向字符串
‘a’
如果ap想指向下一个变量A,只需要ap++即可。
二、const char*是怎么打印的?
我们传入了fmt,fmt的地址为(0x00b07de8)只要++用putc输出即可,注意到这里putc是输入的ascii码对应的十进制值
static int outc(int c)
{
putchar(c); //这里的_out_putchar其实就是putchar,在.h中定义
return 0;
}
三、遇到参数怎么办?
先识别是不是%,如果不是那么就用for循环照常打印fmt:
如果是,那么fmt++跳过%,将ap先指向下一个变量再返回ap-4(当前变量的地址),根据类型分类判断打印出来;
for (; *fmt != ' '; fmt++)
{
if (*fmt != '%') { //顺序查找判断,遇到%就推出,否则继续循环输出
outc(*fmt);
continue;
}
fmt++;
if (*fmt == '0') { //遇到‘0’说明前导码是0
lead = '0';
fmt++;
}
while (*fmt >= '0' && *fmt <= '9') { //紧接着的数字是长度,算出指定长度
maxwidth *= 10;
maxwidth += (*fmt - '0');
fmt++;
}
switch (*fmt) { //判断格式输出
case 'd': out_num(va_arg(ap, int), 10, lead, maxwidth); break;
case 'o': out_num(va_arg(ap, unsigned int), 8, lead, maxwidth); break;
case 'u': out_num(va_arg(ap, unsigned int), 10, lead, maxwidth); break;
case 'x': out_num(va_arg(ap, unsigned int), 16, lead, maxwidth); break;
case 'c': outc(va_arg111(&ap)); break;
//case 'c': outc(va_arg(ap, int)); break;
case 's': outs(va_arg(ap, char*)); break;
default:
outc(*fmt);
break;
}
}
以下是全部代码:
#include//================================================================================================== typedef char* va_list; #define _INTSIZEOF1(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF1(v) ) //ap指向fmt后面的地址 //#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_arg(ap,t) ( *(t *) ( ap=ap + _INTSIZEOF1(t), ap- _INTSIZEOF1(t) ) ) //#define va_arg(ap,t) ( *(t *) ap ) #define va_end(ap) ( ap = (va_list)0 ) //这里就不解释了,不难理解 //================================================================================================== unsigned char hex_tab[] = { '0','1','2','3','4','5','6','7', '8','9','a','b','c','d','e','f' }; //输出各种进制下的字符 static int outc(int c) { putchar(c); //这里的_out_putchar其实就是putchar,在.h中定义 return 0; } static int outs(const char* s) //输出字符串 { while (*s != ' ') putchar(*s++); return 0; } static int out_num(long n, int base, char lead, int maxwidth) { unsigned long m = 0; char buf[256], * s = buf + sizeof(buf); // sizeof算结束符' ' ,strlen不算 int count = 0, i = 0; //注意这里s指向buf的末端,至于为什么继续往下看 *--s = ' '; //先--,在赋值结束符,因为sizeof算结束符在内的长度 if (n < 0) { m = -n; //如果是输出的是负数就取反 } else { m = n; } do { *--s = hex_tab[m % base]; count++; } while ((m /= base) != 0); //将要打印的数字从个位开始一位一位存储在数组buf中,如果上面不是指向buf末端, if (n < 0) *--s = '-'; //负数的话加负号 return outs(s); } //#define va_arg(ap,t) ( *(t *) ( ap=ap + _INTSIZEOF1(t), ap- _INTSIZEOF1(t) ) int va_arg111(va_list * ap) { *ap = *ap +4; //printf("ap+4 va_arg is %pn", *ap); return (*(int*)(*ap - 4)); } static int my_vprintf(const char* fmt, va_list ap) { char lead = ' '; int maxwidth = 0; for (; *fmt != ' '; fmt++) { if (*fmt != '%') { //顺序查找判断,遇到%就推出,否则继续循环输出 outc(*fmt); continue; } fmt++; if (*fmt == '0') { //遇到‘0’说明前导码是0 lead = '0'; fmt++; } while (*fmt >= '0' && *fmt <= '9') { //紧接着的数字是长度,算出指定长度 maxwidth *= 10; maxwidth += (*fmt - '0'); fmt++; } switch (*fmt) { //判断格式输出 case 'd': out_num(va_arg(ap, int), 10, lead, maxwidth); break; case 'o': out_num(va_arg(ap, unsigned int), 8, lead, maxwidth); break; case 'u': out_num(va_arg(ap, unsigned int), 10, lead, maxwidth); break; case 'x': out_num(va_arg(ap, unsigned int), 16, lead, maxwidth); break; case 'c': outc(va_arg111(&ap)); break; //case 'c': outc(va_arg(ap, int)); break; case 's': outs(va_arg(ap, char*)); break; default: outc(*fmt); break; } } return 0; } //reference : int printf(const char *format, ...); int printf0(const char* fmt, ...) { va_list ap; //va_start(ap, fmt); ap = (va_list)&fmt; //printf("ap add: %pn", ap); //printf("fmt : %pn", fmt); //printf("&fmt : %pn", &fmt); ap = (va_list)&fmt + _INTSIZEOF1(fmt); //printf("*ap : %cn", *ap); //printf("ap add: %pn", &ap); //printf("ap : %cn", ap); my_vprintf(fmt, ap); va_end(ap); return 0; } int my_printf_test(void) { printf("%dn", sizeof(char*)); //printf0("My_printf testnr"); printf0("test=%c,%c,%cnr", 'A', 'a','F'); printf0("test decimal number =%dnr", 123456); printf0("test decimal number =%dnr", -123456); printf0("test hex number =0x%xnr", 0x55aa55aa); printf0("test string =%snr", "yoyoyo"); printf0("num=%08dnr", 12345); printf0("num=%8dnr", 12345); printf0("num=0x%08xnr", 0x12345); printf0("num=0x%8xnr", 0x12345); printf0("num=0x%02xnr", 0x1); printf0("num=0x%2xnr", 0x1); printf0("num=%05dnr", 0x1); printf0("num=%5dnr", 0x1); return 0; } int main() { my_printf_test(); return 0; }



