一、源码实现
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <assert.h>
void my_printf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt); /* 用最后一个具有参数的类型的参数去初始化ap */
for (; *fmt; ++fmt)
{
/* 如果不是控制字符 */
if (*fmt != '%')
{
putchar(*fmt); /* 直接输出 */
continue;
}
/* 如果是控制字符,查看下一字符 */
++fmt;
if ('\0' == *fmt) /* 如果是结束符 */
{
assert(0); /* 这是一个错误 */
break;
}
switch (*fmt)
{
case '%': /* 连续2个'%'输出1个'%' */
putchar('%');
break;
case 'd': /* 按照int输出 */
{
/* 下一个参数是int,取出 */
int i = va_arg(ap, int);
putchar(i);
}
break;
case 'c': /* 按照字符输出 */
{
/** 但是,下一个参数是char吗*/
/* 可以这样取出吗? */
char c = va_arg(ap, char);
putchar(c);
}
break;
case 's':
{
char *pc = va_arg(ap, char *);
while(*pc)
putchar(*pc++);
}
break;
}
}
va_end(ap);
return;
}
int main()
{
my_printf("%s %s %c%c%c%c%c!\n", "welcome", "to", 'C', 'h', 'i', 'n', 'a');
return 0;
}
二、缺陷分析
代码编译时会提示警告:
test_printf.c:41:33: warning: ‘char’ is promoted to ‘int’ when passed through ‘...’
char c = va_arg(ap, char);
不处理,直接执行程序,发现程序崩溃了。
问题就在于这行代码:
char c = va_arg(ap, char);
这里面会涉及“默认参数提升”的情况。
C语言中什么时候会牵扯到默认参数提升呢?
在C语言中,调用一个不带原型声明的函数时:调用者会对每个参数执行“默认实际参数提升(default argument promotions)。同时,对可变长参数列表超出最后一个有类型声明的形式参数之后的每一个实际参数,也将执行上述提升工作。
提升工作如下:
-
float 类型的实际参数将提升到 double 。
-
char、short 和相应的 signed、unsigned 类型的实际参数提升到 int 。
-
如果 int 不能存储原值,则提升到 unsigned int 。
所以,调用 my_printf 函数时,传入的参数绝对不会是如下类型:
-
char、signed char、unsigned char
-
short、unsigned short
-
signed short、short int、signed short int、unsigned short int
-
float
所以正确的方案是将代码
char c = va_arg(ap, char);
改为
int c = va_arg(ap, int);
即可。
参考:
(SAW:Game Over!)