Printf這個(gè)函數(shù)讓大家又愛(ài)又恨,第一次接觸c語(yǔ)言編程,基本都是調(diào)用printf打印“Hellow World!”,但當(dāng)真正深入使用編程后,才發(fā)現(xiàn)printf并不是一個(gè)簡(jiǎn)單的函數(shù)。尤其是從事嵌入式軟件工作的開(kāi)發(fā)人員,會(huì)經(jīng)常接觸printf掛接驅(qū)動(dòng)、printf重入的問(wèn)題。
本文詳細(xì)解釋printf函數(shù)的工作原理,希望對(duì)大家有所幫助。
一、函數(shù)棧
分析printf之前首先了解函數(shù)的工作機(jī)制,程序運(yùn)行前需要分配好內(nèi)存空間,如圖1所示(本文給出一個(gè)簡(jiǎn)圖,實(shí)際編譯器分配的會(huì)更加細(xì)致):

圖1
代碼、全局變量、常量?jī)?nèi)存位置固定,堆可以用于分配動(dòng)態(tài)內(nèi)存,而棧區(qū)則用于程序的運(yùn)行。函數(shù)調(diào)用時(shí)將形參從右向左壓入棧,等函數(shù)運(yùn)行完成,通過(guò)出棧,將形參的存儲(chǔ)空間釋放。不同的編譯器對(duì)函數(shù)入棧、出棧的內(nèi)容會(huì)有所區(qū)別,但是對(duì)于c語(yǔ)言,形參的格式遵循_cdedl調(diào)用規(guī)則,有以下特點(diǎn):
函數(shù)形參入棧順序是從右向左
函數(shù)形參存儲(chǔ)空間為連續(xù)存儲(chǔ),且參數(shù)按照固定字節(jié)對(duì)齊;編譯器根據(jù)程序運(yùn)行平臺(tái)的字長(zhǎng)進(jìn)行對(duì)齊,32位字長(zhǎng)平臺(tái)按照4字節(jié)對(duì)齊,64位的會(huì)按照8字節(jié)對(duì)齊。
二、printf函數(shù)棧
printf 函數(shù)原型為int printf(const char *fmt, ...),使用了可變參數(shù)的模式,我們通過(guò)圖2例子來(lái)分析函數(shù)棧。

圖2
fmt:“%d,%c,%c,%f\n”為常量字符串,存儲(chǔ)在內(nèi)存的常量字段,fmt為該字符串首地址;
可變形參1:與變量a類型和數(shù)值一致,為int類型;
可變形參2:與變量b類型和數(shù)值一致, 為char類型;
可變形參3:與變量c類型和數(shù)值一致,為char類型;
可變形參4:與變量d類型和數(shù)值一致,float的可變形參會(huì)被編譯器強(qiáng)制轉(zhuǎn)換為double類型;
假設(shè)該代碼運(yùn)行在32位字長(zhǎng)的平臺(tái),且棧底->棧頂為“高地址->低地址”,函數(shù)棧中所有參數(shù)的存儲(chǔ)地址按照4字節(jié)對(duì)齊存儲(chǔ),設(shè)fmt存儲(chǔ)地址為0x30000000;則其函數(shù)棧如下圖:

圖3
三、printf代碼解析
printf代碼框架
printf代碼及注釋如下所示:
注:本例為32位平臺(tái),所以參數(shù)出入棧地址均為4字節(jié)對(duì)齊。
#ifndef _VALIST
#define _VALIST
typedef char *va_list;
#endif /* _VALIST */
typedef int acpi_native_int;
#define _AUPBND (sizeof (acpi_native_int) - 1) // 入棧4字節(jié)對(duì)齊
#define _ADNBND (sizeof (acpi_native_int) - 1) // 出棧4字節(jié)對(duì)齊
#define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd))) // 4字節(jié)對(duì)齊
#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND)))) // 按照4字節(jié)對(duì)齊取下一個(gè)可變參數(shù),并且更新參數(shù)指針
#define va_end(ap) (void) 0 // 與va_start成對(duì),避免有些編譯器告警
#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND)))) // 第一個(gè)可變形參指針
#endif /* va_arg */
static char sprint_buf[2408];
int printf(const char *fmt, ...)
{
va_list args;
int n;
// 第一個(gè)可變形參指針
va_start(args, fmt);
// 根據(jù)字符串fmt,將對(duì)應(yīng)形參轉(zhuǎn)換為字符串,并組合成新的字符串存儲(chǔ)在sprint_buf[]緩存中,返回字符個(gè)數(shù)。
n = vsprintf(sprint_buf, fmt, args);
//c標(biāo)準(zhǔn)要求在同一個(gè)函數(shù)中va_start 和va_end 要配對(duì)的出現(xiàn)。
va_end(args);
// 調(diào)用相關(guān)驅(qū)動(dòng)接口,將將sprintf_buf中的內(nèi)容輸出n個(gè)字節(jié)到設(shè)備,
// 此處可以是串口、控制臺(tái)、Telnet等,在嵌入式開(kāi)發(fā)中可以靈活掛接
if (console_ops.write)
console_ops.write(sprint_buf, n);
return n;
}
vsprintf解析模式詳解
vsprintf采用%[flags][width][.prec][length][type]模式對(duì)各個(gè)參數(shù)進(jìn)行解析各標(biāo)志解析如下表:
標(biāo)志(flags)
標(biāo)志(flags)用于規(guī)定輸出樣式,含義如下:
flags(標(biāo)志) |
字符名稱 |
描述 |
- |
減號(hào) |
在給定的字段寬度內(nèi)左對(duì)齊,右邊填充空格(默認(rèn)右對(duì)齊) |
+ |
加號(hào) |
強(qiáng)制在結(jié)果之前顯示加號(hào)或減號(hào)(+ 或 -),即正數(shù)前面會(huì)顯示 + 號(hào);默認(rèn)情況下,只有負(fù)數(shù)前面會(huì)顯示一個(gè) - 號(hào) |
(空格) |
空格 |
輸出值為正時(shí)加上空格,為負(fù)時(shí)加上負(fù)號(hào) |
# |
井號(hào) |
specifier 是 o、x、X 時(shí),增加前綴 0、0x、0X; specifier 是 e、E、f、g、G 時(shí),一定使用小數(shù)點(diǎn); specifier 是 g、G 時(shí),尾部的 0 保留 |
0 |
數(shù)字零 |
對(duì)于所有的數(shù)字格式,使用前導(dǎo)零填充字段寬度(如果出現(xiàn)了減號(hào)標(biāo)志或者指定了精度,則忽略該標(biāo)志) |
最小寬度(width)
最小寬度(width)用于控制顯示字段的寬度,即打印輸出的總寬度,取值和含義如下:
width(最小寬度) |
字符名稱 |
描述 |
digit(n) |
數(shù)字 |
字段寬度的最小值,如果輸出的字段長(zhǎng)度小于該數(shù),結(jié)果會(huì)用前導(dǎo)空格填充;如果輸出的字段長(zhǎng)度大于該數(shù),結(jié)果使用更寬的字段,不會(huì)截?cái)噍敵?/span> |
* |
星號(hào) |
寬度在 format 字符串中規(guī)定位置未指定,使用星號(hào)標(biāo)識(shí)附加參數(shù),指示下一個(gè)參數(shù)是width |
精度(.prec)
精度(.precision)用于指定輸出精度,即輸出數(shù)據(jù)占用的寬度,取值和含義如下:
.pre(精度) |
字符名稱 |
描述 |
.digit(n) |
點(diǎn)+數(shù)字 |
對(duì)于整數(shù)說(shuō)明符(d、i、o、u、x、X):precision 指定了要打印的數(shù)字的最小位數(shù)。如果寫入的值短于該數(shù),結(jié)果會(huì)用前導(dǎo)零來(lái)填充。如果寫入的值長(zhǎng)于該數(shù),結(jié)果不會(huì)被截?cái)?。精度?0 意味著不寫入任何字符; 對(duì)于 e、E 和 f 說(shuō)明符:要在小數(shù)點(diǎn)后輸出的小數(shù)位數(shù); 對(duì)于 g 和 G 說(shuō)明符:要輸出的最大有效位數(shù); 對(duì)于 s 說(shuō)明符:要輸出的最大字符數(shù)。默認(rèn)情況下,所有字符都會(huì)被輸出,直到遇到末尾的空字符; 對(duì)于 c 說(shuō)明符:沒(méi)有任何影響; 當(dāng)未指定任何精度時(shí),默認(rèn)為 1。如果指定時(shí)只使用點(diǎn)而不帶有一個(gè)顯式值,則標(biāo)識(shí)其后跟隨一個(gè) 0。 |
.* |
點(diǎn)+星號(hào) |
精度在 format 字符串中規(guī)定位置未指定,使用點(diǎn)+星號(hào)標(biāo)識(shí)附加參數(shù),指示下一個(gè)參數(shù)是精度 |
類型長(zhǎng)度(length)
類型長(zhǎng)度(length)用于控制待輸出數(shù)據(jù)的數(shù)據(jù)類型長(zhǎng)度,取值和含義如下:
length(類型長(zhǎng)度) |
描述 |
h |
參數(shù)被解釋為短整型或無(wú)符號(hào)短整型(僅適用于整數(shù)說(shuō)明符:i、d、o、u、x 和 X) |
l |
參數(shù)被解釋為長(zhǎng)整型或無(wú)符號(hào)長(zhǎng)整型,適用于整數(shù)說(shuō)明符(i、d、o、u、x 和 X)及說(shuō)明符 c(表示一個(gè)寬字符)和 s(表示寬字符字符串) |
ll |
參數(shù)被解釋為超長(zhǎng)整型或無(wú)符號(hào)超長(zhǎng)長(zhǎng)整型,適用于整數(shù)說(shuō)明符(i、d、o、u、x 和 X)及說(shuō)明符 c(表示一個(gè)寬字符)和 s(表示寬字符字符串) |
說(shuō)明符(type)
說(shuō)明符(type)用于規(guī)定輸出數(shù)據(jù)的類型,含義如下:
說(shuō)明符(specifier) |
對(duì)應(yīng)數(shù)據(jù)類型 |
描述 |
d / i |
int |
輸出類型為有符號(hào)的十進(jìn)制整數(shù),i 是老式寫法 |
o |
unsigned int |
輸出類型為無(wú)符號(hào)八進(jìn)制整數(shù)(沒(méi)有前導(dǎo) 0) |
u |
unsigned int |
輸出類型為無(wú)符號(hào)十進(jìn)制整數(shù) |
x / X |
unsigned int |
輸出類型為無(wú)符號(hào)十六進(jìn)制整數(shù),x 對(duì)應(yīng)的是 abcdef,X 對(duì)應(yīng)的是 ABCDEF(沒(méi)有前導(dǎo) 0x 或者 0X) |
f / lf |
double |
輸出類型為十進(jìn)制表示的浮點(diǎn)數(shù),默認(rèn)精度為6(lf 在 C99 開(kāi)始加入標(biāo)準(zhǔn),意思和 f 相同) |
e / E |
double |
輸出類型為科學(xué)計(jì)數(shù)法表示的數(shù),此處 "e" 的大小寫代表在輸出時(shí)用的 “e” 的大小寫,默認(rèn)浮點(diǎn)數(shù)精度為6 |
g |
double |
根據(jù)數(shù)值不同自動(dòng)選擇 %f 或 %e,%e 格式在指數(shù)小于-4或指數(shù)大于等于精度時(shí)用使用 [1] |
G |
double |
根據(jù)數(shù)值不同自動(dòng)選擇 %f 或 %E,%E 格式在指數(shù)小于-4或指數(shù)大于等于精度時(shí)用使用 |
c |
char |
輸出類型為字符型??梢园演斎氲臄?shù)字按照ASCII碼相應(yīng)轉(zhuǎn)換為對(duì)應(yīng)的字符 |
s |
char * |
輸出類型為字符串。輸出字符串中的字符直至遇到字符串中的空字符(字符串以 '\0‘ 結(jié)尾,這個(gè) '\0' 即空字符)或者已打印了由精度指定的字符數(shù) |
p |
void * |
以16進(jìn)制形式輸出指針 |
q |
long long |
輸出類型為長(zhǎng)整型有符號(hào)的十進(jìn)制整數(shù) |
% |
不轉(zhuǎn)換參數(shù) |
不進(jìn)行轉(zhuǎn)換,輸出字符‘%’(百分號(hào))本身 |
n |
int * |
到此字符之前為止,一共輸出的字符個(gè)數(shù),不輸出文本 [4] |
轉(zhuǎn)義字符
轉(zhuǎn)義序列在字符串中會(huì)被自動(dòng)轉(zhuǎn)換為相應(yīng)的特殊字符。printf() 使用的常見(jiàn)轉(zhuǎn)義字符如下:
轉(zhuǎn)義序列 |
描述 |
ASCII 編碼 |
\' |
單引號(hào) |
0x27 |
\" |
雙引號(hào) |
0x22 |
\? |
問(wèn)號(hào) |
0x3f |
\\ |
反斜杠 |
0x5c |
\a |
鈴聲(提醒) |
0x07 |
\b |
退格 |
0x08 |
\f |
換頁(yè) |
0x0c |
\n |
換行 |
0x0a |
\r |
回車 |
0x0d |
\t |
水平制表符 |
0x09 |
\v |
垂直制表符 |
0x0b |
常見(jiàn)組合及輸出結(jié)果
int main(void)
{
int a = 10, b = 3;
printf("%*.*d\n",a,b, -100); // 輸出數(shù)字,右對(duì)齊,寬度從變量獲取
printf("%10.3d\n", -100); // 輸出數(shù)字,右對(duì)齊
printf("%-10.3d\n", -100); // 輸出數(shù)字,左對(duì)齊
printf("%+10.3d\n", 100); // 輸出數(shù)字,正數(shù)帶正號(hào)
printf("%0.13d\n", 100); // 輸出文本格式,如員工號(hào)
printf("%#.8x\n", 0x30ff); // 輸出8位16進(jìn)制地址,小寫字母
printf("%#.8X\n", 0x30ff); // 輸出8位16進(jìn)制地址,大寫字母
printf("%.3f\n", 3.14159267892); // 保留浮點(diǎn)小數(shù)點(diǎn)后有效位數(shù)
printf("%llu\n", 0xffffffffffffffff);// 輸出64位長(zhǎng)整型
}
輸出結(jié)果:

vsprintf流程圖

vsprintf源碼及注釋
#define ZEROPAD 1 /* 無(wú)數(shù)據(jù)位用0填充 */
#define SIGN 2 /* 符號(hào)位 */
#define PLUS 4 /* 符號(hào)位正數(shù)顯示正號(hào) */
#define SPACE 8 /* 符號(hào)位非負(fù)數(shù)顯示空格 */
#define LEFT 16 /* 左對(duì)齊 */
#define SPECIAL 32 /* 顯示其他進(jìn)制的前綴,比如16進(jìn)制添加前綴0x */
#define LARGE 64 /* 使用大寫母 */
#define is_digit(c) ((c) >= '0' && (c) <= '9')
//浮點(diǎn)字符串緩存
#define SZ_NUM_BUF 32
static char sprint_fe[SZ_NUM_BUF+1];
static const char *digits = "0123456789abcdefghijklmnopqrstuvwxyz";
static const char *upper_digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 字符串長(zhǎng)度
static size_t strnlen(const char *s, size_t count)
{
const char *sc;
for (sc = s; *sc != '\0' && count--; ++sc);
return sc - s;
}
// 字符轉(zhuǎn)10進(jìn)制數(shù)
static int skip_atoi(const char **s)
{
int i = 0;
while (is_digit(**s)) i = i*10 + *((*s)++) - '0';
return i;
}
//
static char *number(char *str, long num, int base, int size, int precision, int type)
{
char c, sign, tmp[66];
const char *dig = digits;
int i;
//
if (type & LARGE) dig = upper_digits;
// 左對(duì)齊,去掉補(bǔ)0
if (type & LEFT) type &= ~ZEROPAD;
//
if (base < 2 || base > 36) return 0;
// 補(bǔ)0或補(bǔ)空格
c = (type & ZEROPAD) ? '0' : ' ';
/*符號(hào)位處理,符號(hào)位占用1位-STR*/
sign = 0;
if (type & SIGN)
{
if (num < 0)
{
sign = '-';
num = -num;
size--;
}
// 正數(shù)打印正號(hào)
else if (type & PLUS)
{
sign = '+';
size--;
}
// 符號(hào)位打印空格
else if (type & SPACE)
{
sign = ' ';
size--;
}
}
/*符號(hào)位處理-END*/
/*進(jìn)制轉(zhuǎn)換-STR*/
if (type & SPECIAL)
{
if (base == 16)
size -= 2;
else if (base == 8)
size--;
}
i = 0;
if (num == 0)
tmp[i++] = '0';
else
{
while (num != 0)
{
tmp[i++] = dig[((unsigned long) num) % (unsigned) base];
num = ((unsigned long) num) / (unsigned) base;
}
}
/*進(jìn)制轉(zhuǎn)換-END*/
/*數(shù)據(jù)有效位置處理*/
if (i > precision) precision = i;
size -= precision;
/*非左對(duì)齊且非空余位置不補(bǔ)0,左側(cè)補(bǔ)充空格*/
if (!(type & (ZEROPAD | LEFT))) while (size-- > 0) *str++ = ' ';
// 輸出符號(hào)位
if (sign) *str++ = sign;
// 輸出8進(jìn)制或16進(jìn)制前綴
if (type & SPECIAL)
{
if (base == 8)
*str++ = '0';//0
else if (base == 16)
{
*str++ = '0';
*str++ = digits[33];//0x
}
}
/*左側(cè)補(bǔ)充剩余位,如補(bǔ)0或者補(bǔ)充空格*/
if (!(type & LEFT))
while (size-- > 0) *str++ = c;
/*實(shí)際數(shù)據(jù)小于有效長(zhǎng)度,左側(cè)補(bǔ)0處理*/
while (i < precision--) *str++ = '0';
/*復(fù)制有效字符*/
while (i-- > 0) *str++ = tmp[i];
/*左對(duì)齊,右側(cè)補(bǔ)充空格*/
while (size-- > 0) *str++ = ' ';
return str;
}
// 計(jì)算浮點(diǎn)精度
static int ilog10(double n) /* 在整數(shù)輸出中計(jì)算log10(n) */
{
int rv = 0;
while (n >= 10)
{ /* 右移小數(shù)位 */
if (n >= 100000)
{
n /= 100000; rv += 5;
}
else
{
n /= 10; rv++;
}
}
while (n < 1)
{ /* 左移小數(shù)位 */
if (n < 0.00001)
{
n *= 100000; rv -= 5;
}
else
{
n *= 10; rv--;
}
}
return rv;
}
// 整數(shù)位數(shù)
static double i10x(int n) /* 計(jì)算10^n的整數(shù)輸入 */
{
double rv = 1;
while (n > 0)
{
if (n >= 5)
{
rv *= 100000; n -= 5;
}
else
{
rv *= 10; n--;
}
}
while (n < 0)
{ /* Right shift */
if (n <= -5)
{
rv /= 100000; n += 5;
}
else
{
rv /= 10; n++;
}
}
return rv;
}
// 浮點(diǎn)數(shù)據(jù)轉(zhuǎn)字符串%f,%e,%E
static void ftoa(
char* buf, /* 字符串緩存 */
double val, /* 浮點(diǎn)數(shù) */
int prec, /* 小數(shù)位數(shù) */
char fmt /* 類型標(biāo)識(shí) */
)
{
int d;
int e = 0, m = 0;
char sign = 0;
double w;
const char *er = 0;
const char ds = '.';//FF_PRINT_FLOAT == 2 ? ',' : '.';
unsigned int *pu=(unsigned int *)&val;
// 階碼全1,尾數(shù)不為0,不存在的數(shù)"NaN"
//if (isnan(val))
if(((pu[1]&0x7ff00000)==0x7ff00000)&&(((pu[1]&0xfffff)!=0)||(pu[0]!=0)))
{
er = "NaN";
}
else
{
// 默認(rèn)6 位小數(shù)
if (prec < 0) prec = 6;
// 符號(hào)處理
if (val < 0)
{
val = 0 - val;
sign = '-';
}
else
{
sign = '+';
}
// 階碼全1,尾數(shù)部分全0,符號(hào)位為0表示正無(wú)窮。
// 階碼全1,尾數(shù)部分全0,符號(hào)位為1表示負(fù)無(wú)窮。
//if (isinf(val))
if(((pu[1]&0x7fffffff) ==0x7ff00000)&&(pu[0] == 0))
{
er = "INF";
}
//
else
{
// ‘f’類型
if (fmt == 'f')
{
val += i10x(0 - prec) / 2; /*用于四舍五入,比如1.67保留1位小數(shù)為1.7 */
m = ilog10(val); /*整數(shù)位數(shù)*/
if (m < 0) m = 0;
if (m + prec + 3 >= SZ_NUM_BUF) er = "OV"; /* 最大緩存 */
}
else
{ /* E類型 x.xxxxxxe+xx*/
if (val != 0)
{
val += i10x(ilog10(val) - prec) / 2; /* 用于四舍五入,prec表示底數(shù)部分的小數(shù)位數(shù)*/
e = ilog10(val); /*整數(shù)位數(shù)*/
if (e > 99 || prec + 7 >= SZ_NUM_BUF) //指數(shù)范圍,及最大緩存,
{
er = "OV";
}
else
{
if (e < -99) e = -99;
val /= i10x(e); /* 計(jì)算底數(shù) */
}
}
}
}
// 有效浮點(diǎn)
if (!er)
{
// 符號(hào)位
if (sign == '-')
*buf++ = sign;
do
{
/* 進(jìn)入小數(shù)部分時(shí)插入小數(shù)分隔符 */
if (m == -1)
*buf++ = ds;
//剪掉最高的數(shù)字d
w = i10x(m);
//
d = (int)(val / w); val -= d * w;
*buf++ = (char)('0' + d); /* 記錄10進(jìn)制 */
} while (--m >= -prec);
if (fmt != 'f')
{
*buf++ = (char)fmt;
if (e < 0) {
e = 0 - e; *buf++ = '-';
}
else {
*buf++ = '+';
}
*buf++ = (char)('0' + e / 10);
*buf++ = (char)('0' + e % 10);
}
}
}
// 特殊值
if (er)
{ // 符號(hào)
if (sign) *buf++ = sign;
// 數(shù)據(jù)字符串
do
{
*buf++ = *er++;
} while (*er);
}
*buf = 0; /* 結(jié)束符 */
}
int vsprintf(char *buf, const char *fmt, va_list args)
{
int len;
unsigned long long num;
int i, base;
char * str;
const char *s;/*s所指向的內(nèi)存單元不可改寫,但是s可以改寫*/
int flags; /* flags to number() */
int field_width; /* width of output field */
int precision; /* min. # of digits for integers; max number of chars for from string */
int qualifier; /* 'h', 'l', or 'L' for integer fields */
/* 'z' support added 23/7/1999 S.H. */
/* 'z' changed to 'Z' --davidm 1/25/99 */
for (str=buf ; *fmt ; ++fmt)
{
if (*fmt != '%') /*使指針指向格式控制符'%,以方便以后處理flags'*/
{
*str++ = *fmt;
continue;
}
/* flags */
flags = 0;
repeat:
++fmt;
switch (*fmt)
{
case '-': flags |= LEFT; goto repeat;/*左對(duì)齊-left justify*/
case '+': flags |= PLUS; goto repeat;/*p plus with ’+‘*/
case ' ': flags |= SPACE; goto repeat;/*p with space*/
case '#': flags |= SPECIAL; goto repeat;/*根據(jù)其后的轉(zhuǎn)義字符的不同而有不同含義*/
case '0': flags |= ZEROPAD; goto repeat;/*當(dāng)有指定參數(shù)時(shí),無(wú)數(shù)字的參數(shù)將補(bǔ)上0*/
}
field_width = -1;
if ('0' <= *fmt && *fmt <= '9')
field_width = skip_atoi(&fmt);
else if (*fmt == '*')
{
++fmt;/*skip '*' */
/* it's the next argument */
field_width = va_arg(args, int);// 下個(gè)參數(shù)變量表述位寬
if (field_width < 0) {
field_width = -field_width;
flags |= LEFT;
}
}
/* get the precision-----即是處理.pre 有效位 */
precision = -1;
if (*fmt == '.')
{
++fmt;
if ('0' <= *fmt && *fmt <= '9')
precision = skip_atoi(&fmt);
else if (*fmt == '*') /*如果精度域中是字符'*',表示下一個(gè)參數(shù)指定精度。因此調(diào)用va_arg 取精度值。若此時(shí)寬度值小于0,則將字段精度值取為0。*/
{
++fmt;
/* it's the next argument */
precision = va_arg(args, int);
}
if (precision < 0)
precision = 0;
}
/* get the conversion qualifier 分析長(zhǎng)度修飾符,并將其存入qualifer 變量*/
qualifier = -1;
if (*fmt == 'l' && *(fmt + 1) == 'l')
{
qualifier = 'q';
fmt += 2;
}
else if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L'|| *fmt == 'Z')
{
qualifier = *fmt;
++fmt;
}
/* default base */
base = 10;
/*處理type部分*/
switch (*fmt)
{
case 'c':
/*非左對(duì)齊,左側(cè)填充空格-Satrt*/
if (!(flags & LEFT))
while (--field_width > 0)
*str++ = ' ';
/*非左對(duì)齊,左側(cè)填充空格-End*/
*str++ = (unsigned char) va_arg(args, int);
/*左對(duì)齊,右側(cè)填充空格-Satrt*/
while (--field_width > 0)
*str++ = ' ';
/*左對(duì)齊,右側(cè)填充空格-End*/
//continue在此處是跳出本次for循環(huán)
continue;
case 's':
s = va_arg(args, char *);
if (!s)
s = "";
len = strnlen(s, precision);/*取字符串的長(zhǎng)度,最大為precision*/
/*非左對(duì)齊,左側(cè)填充空格-Satrt*/
if (!(flags & LEFT))
while (len < field_width--)
*str++ = ' ';
/*非左對(duì)齊,左側(cè)填充空格-End*/
for (i = 0; i < len; ++i)
*str++ = *s++;
/*左對(duì)齊,右側(cè)填充空格-Satrt*/
while (len < field_width--)
*str++ = ' ';
/*左對(duì)齊,右側(cè)填充空格-End*/
continue;
case 'p':
/*沒(méi)有設(shè)置寬度域,則默認(rèn)寬度為指針變量長(zhǎng)度,32位系統(tǒng)默認(rèn)為8,且需要添0處理*/
if (field_width == -1)
{
field_width = 2*sizeof(void *);
flags |= ZEROPAD;
}
str = number(str,(unsigned long) va_arg(args, void *), 16,field_width, precision, flags);
continue;
// 形參作為指針變量,向指針變量所指向的地址寫入當(dāng)前轉(zhuǎn)換的字符長(zhǎng)度
case 'n':
// ln長(zhǎng)整型地址
if (qualifier == 'l')
{
long * ip = va_arg(args, long *);
*ip = (str - buf);
}
// zn 字節(jié)地址
else if (qualifier == 'Z')
{
size_t * ip = va_arg(args, size_t *);
*ip = (str - buf);
}
// n 整形地址
else
{
int * ip = va_arg(args, int *);
*ip = (str - buf);
}
continue;
? ? ? ? ? ? // %f %e %E %lf均使用double類型
? ? case 'f': /* Floating point (decimal) */
? ? case 'e': /* Floating point (e) */
? ? case 'E': /* Floating point (E) */
? ? // double數(shù)據(jù)轉(zhuǎn)字符串
? ? ftoa(sprint_fe, va_arg(args, double), precision, *fmt); /* 浮點(diǎn)轉(zhuǎn)字符串*/
? ? // 右對(duì)齊 左側(cè)補(bǔ)充空格
? ? if (!(flags&LEFT))
? ? {
? ? for (j = strnlen(sprint_fe, SZ_NUM_BUF); j<field_width; j++)
*str++= '/0';
? ? }
? ? // 數(shù)據(jù)主體
? ? i = 0;
? ? while(sprint_fe[i]) *str++ = sprint_fe[i++]; /* 主體 */
? ? // 左對(duì)齊 右側(cè)補(bǔ)充空格
? ? while (j++ < field_width) *str++ = '/0';
? ? continue;
// %%表示%
case '%':
*str++ = '%';
continue;
/* 設(shè)置進(jìn)制*/
case 'o':
base = 8;
break;
/*大寫*/
case 'X':
flags |= LARGE;
case 'x':
base = 16;
break;
/*有符號(hào)類型*/
case 'd':
case 'i':
flags |= SIGN;
/*無(wú)符號(hào)類型*/
case 'u':
break;
default:
/*非參數(shù)打印*/
*str++ = '%';
if (*fmt)
*str++ = *fmt;
else
--fmt;
continue;
}
/*同時(shí)如果flags有符號(hào)位的話,將參數(shù)轉(zhuǎn)變成有符號(hào)的數(shù)*/
if (qualifier == 'l')
{
num = va_arg(args, unsigned long);
if (flags & SIGN)
num = (signed long) num;
}
else if (qualifier == 'q')
{
num = va_arg(args, unsigned long long);
if (flags & SIGN)
num = (signed long long) num;
}
else if (qualifier == 'Z')
{
num = va_arg(args, size_t);
}
else if (qualifier == 'h')
{
num = (unsigned short) va_arg(args, int);
if (flags & SIGN)
num = (signed short) num;
}
else
{
num = va_arg(args, unsigned int);
if (flags & SIGN)
num = (signed int) num;
}
str = number(str, num, base, field_width, precision, flags);
}
*str = '/0';/*最后在轉(zhuǎn)換好的字符串上加上NULL*/
return str-buf;/*返回轉(zhuǎn)換好的字符串的長(zhǎng)度值*/
}
四、printf不可重入
Printf函數(shù)是不可重入的,因?yàn)関sprintf函數(shù)調(diào)用了全局變量sprint_buf[],但是并且沒(méi)有做任何邊界保護(hù),如果在多線程、或者中斷程序中運(yùn)行該函數(shù),就難以避免資源重復(fù)搶占的問(wèn)題。比如,線程A和線程B均調(diào)用了printf打印,線程A正在運(yùn)行vsprintf函數(shù),對(duì)sprint_buf正在操作中,此時(shí)被高優(yōu)先級(jí)線程B搶占,B獲取了sprint_buf的操作權(quán),線程A生的數(shù)據(jù)就會(huì)被覆蓋掉。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-675839.html
五、解決不可重入的方法
在嵌入式軟件開(kāi)發(fā)中經(jīng)常會(huì)使用LogMsg打印,LogMsg可以自己編寫,也可以借用操作系統(tǒng)的自帶的,其根本的思想就是不同線程使用不同的sprint_buf緩存空間。主要方法有獨(dú)立線程使用靜態(tài)消息隊(duì)列、申請(qǐng)動(dòng)態(tài)內(nèi)存、使用多組緩存空間等。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-675839.html
到了這里,關(guān)于C語(yǔ)言Printf函數(shù)深入解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!