一、源碼實作
#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);
即可。
參考:
https://blog.csdn.net/astrotycoon/article/details/8284501
https://blog.csdn.net/iynu17/article/details/51588199
(SAW:Game Over!)