国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C語(yǔ)言Printf函數(shù)深入解析

這篇具有很好參考價(jià)值的文章主要介紹了C語(yǔ)言Printf函數(shù)深入解析。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。

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ì)致):

printf函數(shù)原型,c語(yǔ)言,數(shù)據(jù)結(jié)構(gòu),Powered by 金山文檔

圖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):

  1. 函數(shù)形參入棧順序是從右向左

  1. 函數(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ù)棧。

printf函數(shù)原型,c語(yǔ)言,數(shù)據(jù)結(jié)構(gòu),Powered by 金山文檔

圖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ù)棧如下圖:

printf函數(shù)原型,c語(yǔ)言,數(shù)據(jù)結(jié)構(gòu),Powered by 金山文檔

圖3

三、printf代碼解析

  1. 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;
}
  1. vsprintf解析模式詳解

vsprintf采用%[flags][width][.prec][length][type]模式對(duì)各個(gè)參數(shù)進(jìn)行解析各標(biāo)志解析如下表:

  1. 標(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)志)

  1. 最小寬度(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

  1. 精度(.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ù)是精度

  1. 類型長(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(表示寬字符字符串)

  1. 說(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]

  1. 轉(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é)果:

printf函數(shù)原型,c語(yǔ)言,數(shù)據(jù)結(jié)構(gòu),Powered by 金山文檔
  1. vsprintf流程圖

printf函數(shù)原型,c語(yǔ)言,數(shù)據(jù)結(jié)構(gòu),Powered by 金山文檔
  1. 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ì)被覆蓋掉。

五、解決不可重入的方法

在嵌入式軟件開(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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 在 C 語(yǔ)言中添加注釋、包含頭文件和使用 printf 函數(shù)輸出字符串的方法

    注釋是程序員在編寫代碼時(shí)添加給自己或其他人的說(shuō)明文字,用來(lái)提高代碼的可讀性,方便理解。注釋通常會(huì)添加在代碼的關(guān)鍵部分以及不易理解的地方。 在C語(yǔ)言中,注釋可以使用兩種方式:?jiǎn)涡凶⑨屢约岸嘈凶⑨尅涡凶⑨屖褂肻\\"http://\\\",多行注釋使用\\\"/* */\\\"。多行注釋不可嵌套

    2023年04月22日
    瀏覽(25)
  • C語(yǔ)言(輸入輸出函數(shù)getchar,putchar、gets、puts,scanf,printf的功能以及用法)

    C語(yǔ)言(輸入輸出函數(shù)getchar,putchar、gets、puts,scanf,printf的功能以及用法)

    int getchar( void ); 返回值為int,所以需要用一個(gè)int變量來(lái)接收,不管輸入幾個(gè)字符,每次都只接收第一個(gè)字符,常與while和putchar配合使用。 從下面這張圖可以看出,輸入一個(gè)空格也會(huì)打印 當(dāng)然,獲取一個(gè)字符用得不多,每次都需要獲取一串,所以我們可以配合while來(lái)使用。 用

    2024年02月02日
    瀏覽(24)
  • C語(yǔ)言基本語(yǔ)句(變量類型int、 float、 double、 char,函數(shù)scanf、printf、putchar()、getchar() )

    1. int, float, double, char ①整型int(對(duì)應(yīng)%d) ?int a,b; ?scanf(\\\"%d,%d\\\",a,b); printf (\\\"%d\\\",a); printf(\\\"我今天吃了%d個(gè)蘋果,在黑板上寫下整數(shù)%d,這很有趣。\\\",a,b); //printf(\\\"……\\\",變量名)中,“……”部分內(nèi)容比較自由,可隨便發(fā)揮,但必須包括%d,幾個(gè)變量名就對(duì)應(yīng)幾個(gè)%d ②單精度型浮點(diǎn)數(shù)

    2024年02月08日
    瀏覽(30)
  • stm32---在keil 5中使用printf函數(shù)輸出到串口USART(printf函數(shù)的移植方法)以及補(bǔ)充窗口printf函數(shù)輸出漢字

    stm32---在keil 5中使用printf函數(shù)輸出到串口USART(printf函數(shù)的移植方法)以及補(bǔ)充窗口printf函數(shù)輸出漢字

    在串口函數(shù)初始化的c文件中加上#include stdio.h 重寫fputc函數(shù)?(fputc是printf函數(shù)的底層,printf就是不斷調(diào)用fputc函數(shù)一個(gè)個(gè)打印,把fputc函數(shù)重定向到串口,這樣子printf自然就輸出到串口) 在主函數(shù)main.c編寫如下代碼后直接運(yùn)行. 引入#include stdarg.h ?對(duì)sprintf進(jìn)行可變參數(shù)的函數(shù)封裝 在主

    2024年02月05日
    瀏覽(39)
  • 【printf函數(shù)的具體用法】

    1、基本輸出語(yǔ)句 printf()函數(shù)是C語(yǔ)言的輸出函數(shù),功能是按指定的輸出格式把相應(yīng)的參數(shù)值在標(biāo)準(zhǔn)的輸出設(shè)備中顯示出來(lái)。 printf(格式控制串,參數(shù)1,參數(shù)2,…),可能這樣寫出的格式不好理解,現(xiàn)在細(xì)分一下類型,其中可分為四種格式: 1、字符串型 printf(“字符串n”) 以上

    2024年02月16日
    瀏覽(22)
  • STM32使用printf函數(shù)

    STM32使用printf函數(shù)

    正點(diǎn)原子學(xué)習(xí)筆記 1,printf函數(shù)輸出流程 2,printf的使用 3,printf函數(shù)支持 1)用戶調(diào)用 printf() 2)編譯器從 stdio.h 里解析 3)最終由 fputc() 實(shí)現(xiàn)輸出 由此用戶需要根據(jù)最終輸出的硬件重新定義該函數(shù),此過(guò)程稱為: printf重定向 1,printf(\\\"Hello World!rn\\\"); 2,printf(\\\"%drn\\\",121); ? ? ? ? ?

    2024年02月07日
    瀏覽(19)
  • Linux printf 函數(shù)輸出問(wèn)題

    ? ? ? ? printf 函數(shù)并不會(huì)直接將數(shù)據(jù)輸出到屏幕,而是先放到緩沖區(qū)中,只有一下三種情況滿足,才會(huì)輸出到屏幕。 ? ?1) 緩沖區(qū)滿 ? ?2) 強(qiáng)制刷新緩沖區(qū) fflush ? ?3) 程序結(jié)束時(shí) 編譯運(yùn)行: gcc main.c -o main ./main hello world ? 我們發(fā)現(xiàn)屏幕上輸出hello world 后,光標(biāo)閃爍三秒

    2024年02月06日
    瀏覽(41)
  • printf函數(shù)格式控制符

    提示:文章寫完后,目錄可以自動(dòng)生成,如何生成可參考右邊的幫助文檔 文章目錄 前言 一、printf函數(shù)格式控制符 二、使用步驟 1.引入庫(kù) 2.讀入數(shù)據(jù) 總結(jié) 提示:這里可以添加本文要記錄的大概內(nèi)容: 例如:隨著人工智能的不斷發(fā)展,機(jī)器學(xué)習(xí)這門技術(shù)也越來(lái)越重要,很多人

    2024年02月10日
    瀏覽(13)
  • 在STM32中使用printf函數(shù)

    在STM32中使用printf函數(shù)

    ????利用printf函數(shù)使得輸出打印到串口中。 ????我們?cè)贑語(yǔ)言中為什么能夠調(diào)用printf函數(shù)打印數(shù)據(jù)到控制臺(tái)中,那是因?yàn)閜rintf函數(shù)又調(diào)用了fputc()函數(shù)。那么我們我們可不可以說(shuō)printf函數(shù)之所以能夠打印數(shù)據(jù)到控制臺(tái)中幾乎都是fput()函數(shù)的功勞呢?答案是顯而易見(jiàn)的。

    2024年02月16日
    瀏覽(13)
  • 【基礎(chǔ)】STM32中如何使用printf()函數(shù)

    在STM32串口通信程序中使用printf發(fā)送數(shù)據(jù),非常的方便。可在剛開(kāi)始使用的時(shí)候總是遇到問(wèn)題,常見(jiàn)的是硬件訪真時(shí)無(wú)法進(jìn)入main主函數(shù),其實(shí)只要簡(jiǎn)單的配置一下就可以了。 下面就說(shuō)一下使用printf需要做哪些配置。 有兩種配置方法: 一、對(duì)工程屬性進(jìn)行配置,詳細(xì)步驟如下

    2024年04月24日
    瀏覽(18)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包