目录
1.可变参数原理
1.1 函数参数入栈原理
1.2 可变参数如何实现?
1.2.1 可变参数实现原理
1.2.2 固定参数有什么用?
1.2.3 va_start,va_arg,va_end如何使用?
2.printf函数实现原理
2.1 printf函数流程
2.2 printf函数格式解析原理
2.2.1 printf函数原型
2.2.2 printf格式解析
3.实现一个简易版printf函数
图 1-1 函数参数入栈原理
函数参数采用从右至左的方式入栈,最右的参数从栈顶入栈,以此类推,每个参数都会存储在栈中,形成一个函数参数列表。
可变参数能够实现的根本原因是程序知道如何解析存储在栈中的函数参数列表。
可变参数函数原型由固定参数和可变参数组成,如:
int func(fmt,...);
前面我们已经知道函数参数是如何存储在栈中,要实现可变参数,我们需要从栈中还原出可变参数的每一个参数,如何才能从栈中还原出可变参数呢?
这个我们需要用到系统提供的方法:va_start,va_arg,va_end。
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( (void)(ap = (va_list)&v + _INTSIZEOF(v)) )
#define va_arg(ap,t) (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t)))
#define va_end(ap) ( (void)(ap = (va_list)0))
宏#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))解析?
_INTSIZEOF(n)作用为计算传入参数n在内存中占用的字节数,以4字节对齐。 因为是以4字节对齐,不足4字节的部分依然占用4字节。
举个栗子:
char类型,_INTSIZEOF(char) = ((sizeof(char) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (1+4 -1) & ~(4 -1) = 0x00000004 & 0xfffffff3= 4;
short类型,_INTSIZEOF(short) = ((sizeof(short) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (2+4 -1) & ~(4 -1) = 0x00000005 & 0xfffffff3= 4;
int类型,_INTSIZEOF(int) = ((sizeof(int) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (4+4 -1) & ~(4 -1) = 0x00000007 & 0xfffffff3= 4;
long long int类型,_INTSIZEOF(long long int) = ((sizeof(long long int) + sizeof(int) - 1) & ~(sizeof(int) - 1)) = (8+4 -1) & ~(4 -1) = 0x0000000b & 0xfffffff3= 8;
固定参数主要有一下两个作用:
(1)定位可变参数的地址。
固定参数是一个实际的参数,我们很容易知道固定参数的地址,因为固定参数和可变参数地址上是连续的,知道固定参数地址后,我们能够通过固定参数地址获取到每个可变参数地址。
(2)定义可变参数遍历规则。
固定参数提供一些关键信息用于遍历可变参数,通常我们用的比较多的有以下情况:
图 1-2 vva_start,va_arg,va_end如何使用
a.使用va_list定义一个指针变量ap。
b.调用va_start(ap, 固定参数1),将va_list指针变量指向固定参数1后的第一个地址。
c.每次调用va_arg(ap, 参数类型),va_arg会将va_list指针变量指向下一个可变参数地址,同时返回当前可变参数的值。
d.调用va_end,将va_list指针变量清零,防止va_list指针变量变成野指针。
图 2-1 printf函数流程
printf函数称为格式化IO函数,printf函数和普通输出函数(如:fputs,puts等函数)的区别在于多了格式化的操作,格式化就是可变参数解析。
printf函数需要实现两个功能:
#include
int printf(const char *format, ...);
函数参数:
format:固定参数,格式化字符串,用于解析可变参数。
...:可变参数。
返回值:
成功:返回一个整型值,表示成功输出的字符数。
失败:返回一个负数。
printf函数格式由一个个基本格式“%[标志][宽度][.精度][长度]类型”组成。
[]为可选项。
其他项为必选项。
表 2-1 printf格式
表 2-2 标志表
表 2-3 宽度表
表 2-4 精度表
表 2-5 长度表
表 2-6 类型表
printf通常还需用到个特殊的字符表(转义字符表),如下表:
表 2-7 转义字符表
#include
#include
#include
#include
#include
int my_printf(const char *format, ...) {
va_list ap;
#define BUF_SIZE (1024)
#define STR_SIZE (64)
char buf[BUF_SIZE] = {0};
char c;
char *pos = buf;
va_start(ap, format);
while((c = *format)) {
if (c == '%') {
format++;
c = *format;
switch(c) {
case 'd':
{
int value = va_arg(ap, int);
char str[STR_SIZE] = {0};
sprintf(str, "%d", value);
memcpy(pos, str, strlen(str));
pos += strlen(str);
break;
}
case 'u':
{
unsigned int value = va_arg(ap, unsigned int);
char str[STR_SIZE] = {0};
sprintf(str, "%u", value);
memcpy(pos, str, strlen(str));
pos += strlen(str);
break;
}
case 'o':
{
unsigned int value = va_arg(ap, unsigned int);
char str[STR_SIZE] = {0};
sprintf(str, "%o", value);
memcpy(pos, str, strlen(str));
pos += strlen(str);
break;
}
case 'x':
{
unsigned int value = va_arg(ap, unsigned int);
char str[STR_SIZE] = {0};
sprintf(str, "%x", value);
memcpy(pos, str, strlen(str));
pos += strlen(str);
break;
}
case 'c':
{
char value = va_arg(ap, int);
memcpy(pos, &value, 1);
pos++;
break;
}
break;
case 's':
{
char *value = va_arg(ap, char *);
memcpy(pos, value, strlen(value));
pos += strlen(value);
break;
}
case 'p':
{
long long *value = va_arg(ap, long long *);
char str[STR_SIZE] = {0};
sprintf(str, "%p", value);
memcpy(pos, str, strlen(str));
pos += strlen(str);
break;
}
case '%':
{
memcpy(pos, &c, 1);
pos++;
break;
}
default:
{
printf("format error");
return -1;
}
}
} else {
memcpy(pos, &c, 1);
pos++;
}
format++;
}
write(2, buf, strlen(buf));
return 0;
}
int main(int argc, char *argv[]) {
int num = 0;
my_printf("%d,%u,%o,%x,%c,%s,%p,%%\n", 1234, 1234, 1234, 1234, 'a', "myprintf", &num);
return 0;
}