首先,printf() 是一个不定形参函数,这一点我们可以通过省略符(...)来实现。
其次,我们怎么获取到这些不定实参呢?看个例子:
void func(int a, ...)
{
}
int main()
{
func(1, 2, 3);
return 0;
}
这里定义了一个 func 函数,使用了省略符,然后在 main 方法中我们调用了它,并传入了3个实参1,2,3,在编译时会生成这个函数的实例。
在函数调用时,实参将会从右到左依次入栈,在内存栈中的地址是连续的,所以我们只要取得其中任意一个实参的地址,就能通过对指针操作从而获得栈中其他参数,下图是给出例子反汇编时的一部分:
可以发现,在 func(1, 2, 3)被调用时,执行了push 3,push 2,push 1这3个汇编指令。
看完原理直接来读代码,先配个图:
#include<iostream>
typedef char byte;
int printf(char *str, ...)
{
char buff[1024]; //用来存放格式化后的字符串
const byte *stack_ptr = nullptr; //定义一个 byte 型的指针,用来操作内存栈,每个内存单元为一个 byte
int pos = 0; //指向当前 buff '\0' 位置
stack_ptr = (byte*)&str; //指向第一个参数的地址(5)
stack_ptr += sizeof(char *); //将 stack_ptr 移动 sizeof(char *) 个内存单元以指向第二个参数起始地址(9)
for (char *ptr = str; *ptr; ptr++) //遍历 str 找出所有 %d,%s
{
char ch = *ptr;
if (ch != '%')
{
buff[pos++] = ch; //如果不是 '%' 则直接放入 buff 中并将 pos+1
continue;
}
else
{
ch = *(++ptr); //如果是 '%' 则取其后面的字符
if (ch == 's') //如果是 's' (%s)
{
const char *s = *(char**)stack_ptr; //根据 %s 判断出此参数为 char* 类型,将 stack_ptr 转化为指向 char* 类型的指针(char**)并取其值
memcpy(buff + pos, s, strlen(s)); //将此参数指向字符串加入 buff 之中
pos += strlen(s); //更新 pos
stack_ptr += sizeof(char *); //将 stack_ptr 移动sizeof(char *) 个内存单元以指向下一个参数起始地址(17)
}
else if (ch == 'd')
{
char num_s[25]; //根据 %d 判断出此参数为 int 类型
itoa(*(int*)stack_ptr, num_s, 10); //将 stack_ptr 转化为指向 int 类型的指针(int*)并取其值传给 itoa 转化为字符串保存至 num_s
memcpy(buff + pos, num_s, strlen(num_s)); //将此字符串加入 buff 之中
pos += strlen(num_s); //更新 pos
stack_ptr += sizeof(int); //将 stack_ptr 移动sizeof(int) 个内存单元以指向下一个参数起始地址(13)
}
}
}
puts(buff); //输出 buff
return 0;
}
int main()
{
auto s = "木头人";
printf("%d %s\n", 123, s);
system("pause");
return 0;
}