本文参考: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,%c\n\r", 'a', 'A','F');
那么fmt地址指向字符串
test=%c,%c,%c\n\r
ap指向字符串
‘a’
如果ap想指向下一个变量A,只需要ap++即可。
那么va_arg是什么意思呢?
#define va_arg(ap,t) (*(t *)( ap=ap + _INTSIZEOF1(t), ap- _INTSIZEOF1(t)))
起初我不理解#define的逗号是怎么用的上网找了不少,但是都是说逗号表达式,并没有详细的解析,那就只能自己把它猜测的拆开单步调试了;
程序里我自己定义了一个**va_arg111(&ap)*函数,就是为了可以还原va_arg的本来面目。由注释和前面的地址图知道ap是一个返回类型为char(va_list)类型的指针,里面保存的内容是printf函数各个变量的首地址,如果,那么va_arg的目的是可以让ap指针指向下一个变量的地址,并且返回当前变量的地址给需要用到的函数。
也就是这个意思:
int va_arg111(va_list * ap)
{
*ap = *ap +4;
//printf("ap+4 va_arg is %p\n", *ap);
return (*(int*)(*ap - 4));
}
我在输出字符的时候做了成功的替换:
case 'c': outc(va_arg111(&ap)); break;
二、const char*是怎么打印的?
我们传入了fmt,fmt的地址为(0x00b07de8)只要++用putc输出即可,注意到这里putc是输入的ascii码对应的十进制值
static int outc(int c)
{
putchar(c); //这里的_out_putchar其实就是putchar,在.h中定义
return 0;
}
三、遇到参数怎么办?
先识别是不是%,如果不是那么就用for循环照常打印fmt:
如果是,那么fmt++跳过%,用while识别%后的数字参数如%08d(注意这里还没有识别小数点),用switch来识别类型如%d%o。
如果遇到负数,那么将其传递至打印int的函数里,然后将其转换为字符串,在这里需要注意是倒着转换的,先让buf指向最末尾的位置,然后取余数(取低位)。
static int out_num(long n, int base, char lead, int maxwidth)
{
unsigned long m = 0;
char buf[256], * s = buf + sizeof(buf); // sizeof算结束符'\0' ,strlen不算
int count = 0, i = 0; //注意这里s指向buf的末端,至于为什么继续往下看
*--s = '\0'; //先--,在赋值结束符,因为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);
}
之后,利用va_arg函数再将ap先指向下一个变量再返回ap-4(当前变量的地址),根据类型分类判断打印出来;
for (; *fmt != '\0'; 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<stdio.h>
//==================================================================================================
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 != '\0')
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算结束符'\0' ,strlen不算
int count = 0, i = 0; //注意这里s指向buf的末端,至于为什么继续往下看
*--s = '\0'; //先--,在赋值结束符,因为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 %p\n", *ap);
return (*(int*)(*ap - 4));
}
/*reference : int vprintf(const char *format, va_list ap); */
static int my_vprintf(const char* fmt, va_list ap)
{
char lead = ' ';
int maxwidth = 0;
for (; *fmt != '\0'; 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: %p\n", ap);
//printf("fmt : %p\n", fmt);
//printf("&fmt : %p\n", &fmt);
ap = (va_list)&fmt + _INTSIZEOF1(fmt);
//printf("*ap : %c\n", *ap);
//printf("ap add: %p\n", &ap);
//printf("ap : %c\n", ap);
my_vprintf(fmt, ap);
va_end(ap);
return 0;
}
int my_printf_test(void)
{
printf("%d\n", sizeof(char*));
//printf0("My_printf test\n\r");
printf0("test=%c,%c,%c\n\r", 'A', 'a','F');
printf0("test decimal number =%d\n\r", 123456);
printf0("test decimal number =%d\n\r", -123456);
printf0("test hex number =0x%x\n\r", 0x55aa55aa);
printf0("test string =%s\n\r", "yoyoyo");
printf0("num=%08d\n\r", 12345);
printf0("num=%8d\n\r", 12345);
printf0("num=0x%08x\n\r", 0x12345);
printf0("num=0x%8x\n\r", 0x12345);
printf0("num=0x%02x\n\r", 0x1);
printf0("num=0x%2x\n\r", 0x1);
printf0("num=%05d\n\r", 0x1);
printf0("num=%5d\n\r", 0x1);
return 0;
}
int main()
{
my_printf_test();
return 0;
}