天天看點

SQLite3源碼學習(19) printf的實作 1.可變參數 2.架構和接口 3.sqlite3_mprintf分析 4.相關函數

在SQLite中并沒有使用标準庫的printf()函數,而是自己實作了printf的全部功能并針對不同的應用做了一層封裝。所有相關代碼在printf.c裡,下面就來分析SQLite是如何實作自己的printf。

1.可變參數

       可變參數是實作printf的基礎,其聲明格式如下:

printf(const char *zFormat, ...)

在提取函數的參數時需要用到va_start,va_arg,va_end這3個宏定義,一般編譯都内置了這3個宏,不同的平台可能略微不同。

下面以win32平台的實作為例,棧的位址是從高位址到低位址擴充,而參數的位址入棧順序是從右向左,是以zFormat參數的位址最小,後面參數的位址依次增大。

SQLite3源碼學習(19) printf的實作 1.可變參數 2.架構和接口 3.sqlite3_mprintf分析 4.相關函數

那麼,知道第一個固定參數的位址之後,後面的參數位址也就确定了。

typedef char * va_list; 

  #define _INTSIZEOF(n) \ 
  ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) ) 

  #define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) ) // &v是固定參數的位址,ap是第一個可變參數的位址

  #define va_arg(ap,t) \ 
  ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) //取得位址ap中的參數值,取到之後讓ap指向下一個參數,t是參數的類型

    #define va_end(ap) ( ap = (va_list)0 )
           

是以printf的大概實作是這樣的

void printf(const char *format,...)
{
    va_list ap;
    va_list p;
   
    va_start(ap,format);     //将ap指向第一個實際參數的位址
    ……
    int arg = va_arg(ap,int);//參數的類型在format裡解析得到
    ……
    va_end(ap);    
}
           

2.架構和接口

  sqlite3VXPrintf()函數是SQLite中實作printf的核心,所有的接口最終都會調用這個函數,其聲明如下:

void sqlite3VXPrintf(
  StrAccum *pAccum,          /* Accumulate results here */
  const char *fmt,           /* Format string */
  va_list ap                 /* arguments */
){
}
           

這個函數會把格式字元串fmt根據相應的參數ap替換成最終的結果存在字元累加器pAccum中。

舉個簡單的例子,如

printf("a:%d b:%d",1,2);

其中fmt是"a:%d b:%d",參數是1,2,最後結果是"a:1 b:2"。

sqlite3VXPrintf()函數的實作要針對各種不同的格式,是以非常繁瑣和細碎,将在下一篇中詳細介紹。

printf.c的所有對外接口如下圖所示:

SQLite3源碼學習(19) printf的實作 1.可變參數 2.架構和接口 3.sqlite3_mprintf分析 4.相關函數

sqlite3DebugPrintf和printf的功能完全一樣,都是把列印資訊輸出到stdout。

sqlite3_mprintf并沒有輸出到stdout,而是傳回最後的結果的指針。

sqlite3_snprintf功能和snprintf相同,把結果儲存到輸入參數的目标位址zBuf裡。

sqlite3VMPrintf相比上面幾個接口還支援内部%格式轉換的擴充。

sqlite3_log用來輸出錯誤資訊,需要使用者先注冊回調函數,把結果傳入回調函數。

sqlite3XPrintf就是sqlite3VXPrintf加上va_start和va_end。

3.sqlite3_mprintf分析

現在主要分析sqlite3_mprintf()函數,其他的接口都類似。

先獲得傳入的變參位址,再調用sqlite3_vmprintf()函數

char *sqlite3_mprintf(const char *zFormat, ...){
  va_list ap;
  char *z;
  va_start(ap, zFormat);
  z = sqlite3_vmprintf(zFormat, ap);
  va_end(ap);
  return z;
}
           

在sqlite3_vmprintf裡定義了StrAccum acc,acc主要用來儲存格式轉換後的字元串内容和長度,StrAccum的定義如下:

/*
** An objected used to accumulate the text of a string where we
** do not necessarily know how big the string will be in the end.
*/
struct StrAccum {
  sqlite3 *db;         /* Optional database for lookaside.  Can be NULL */
  char *zBase;         /* A base allocation.  Not from malloc. */
  char *zText;         /* The string collected so far */
  u32  nChar;          /* Length of the string so far */
  u32  nAlloc;         /* Amount of space allocated in zText */
  u32  mxAlloc;        /* Maximum allowed allocation.  0 for no malloc usage */
  u8   accError;       /* STRACCUM_NOMEM or STRACCUM_TOOBIG */
  u8   printfFlags;    /* SQLITE_PRINTF flags below */
};
           

現在再來看sqlite3_vmprintf的實作,一開始并不知道最後輸出的字元串大小,先傳入一個zBase數組作為緩存,如果發現需要存儲的空間超過了zBase再重新申請更大的空間。

char *sqlite3_vmprintf(const char *zFormat, va_list ap){
  char *z;
  char zBase[SQLITE_PRINT_BUF_SIZE];
  StrAccum acc;

  sqlite3StrAccumInit(&acc, 0, zBase, sizeof(zBase), SQLITE_MAX_LENGTH);//初始化acc,傳入臨時空間zBase
  sqlite3VXPrintf(&acc, zFormat, ap);
  z = sqlite3StrAccumFinish(&acc);// zBase是一個臨時變量,函數傳回就會釋放,最後的結果存在zText裡,如果沒有重新申請空間,那麼zText就是zBase,是以這個函數給zText重新申請記憶體
  return z;
}
           

4.相關函數

Ÿ  void sqlite3StrAccumAppend(StrAccum *p, const char *z, int N)

添加N位元組的z字元串到p->zText裡,如果有必要通過enlargeAndAppend申請更大的記憶體空間。

Ÿ  void sqlite3StrAccumAppendAll(StrAccum *p, const char *z)

把整個strlen(z)大小的z字元串添加到p->zText裡

Ÿ  void sqlite3AppendChar(StrAccum *p, int N, char c)

添加N個字元c到p->zText裡

Ÿ  static void SQLITE_NOINLINE enlargeAndAppend(StrAccum *p, const char *z, int N)

如果p->nChar+N >= p->nAlloc,即所需空間超過已有空間時,為p->zText配置設定更多的記憶體空間,并把z裡的内容複制到新的空間裡。

Ÿ  static int sqlite3StrAccumEnlarge(StrAccum *p, int N)

為p->zText重新配置設定p->nChar+N+1+p->nChar大小的空間,實際所需的大小為p->nChar+N+1,之是以配置設定更大的空間是為了防止頻繁地調用malloc。