在SQLite中并沒有使用标準庫的printf()函數,而是自己實作了printf的全部功能并針對不同的應用做了一層封裝。所有相關代碼在printf.c裡,下面就來分析SQLite是如何實作自己的printf。
1.可變參數
可變參數是實作printf的基礎,其聲明格式如下:
printf(const char *zFormat, ...)
在提取函數的參數時需要用到va_start,va_arg,va_end這3個宏定義,一般編譯都内置了這3個宏,不同的平台可能略微不同。
下面以win32平台的實作為例,棧的位址是從高位址到低位址擴充,而參數的位址入棧順序是從右向左,是以zFormat參數的位址最小,後面參數的位址依次增大。
那麼,知道第一個固定參數的位址之後,後面的參數位址也就确定了。
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的所有對外接口如下圖所示:
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。