SQLite3源碼學習(19) printf的實現
在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。
智能推薦
3. django的sqlite3設置
1. 每次執行python manage.py runserver都會出現下圖(標紅的信息) 1.1 怎么把這個提示去掉 sqlite3需要先將服務運行一次后才會出現 python manage.py runserver 2. 然后輸入下面的語句 2.1 出現下圖的提示信息 3. 然后再重新運行服務 3.1 這樣就不會出現紅字了 4.1 因為執行了python manage.py migrate,...
sqlite3增刪改查C++類實現
直接上代碼 mydll.cpp代碼如下: mydll.h代碼如下: vs編譯產生以下2個文件 調用代碼如下 運行結果: 工程下載鏈接: https://download.csdn.net/download/wzhrsh/12403939 ...
使用C封裝Sqlite3實現簡單API
以下是對sqlite3接口的簡單封裝,這里只實現了幾個最基本的接口,喜歡的朋友可以繼續擴展 過程中參考了下面的文章 https://www.runoob.com/sqlite/sqlite-c-cpp.html 編譯方法:gcc sqlite_test.c sqlite_api.c -lsqlite3 運行: ./a.out 接口頭文件 API實現 測試程序如下 測試結果如下: 長按二維碼關注【嵌...
使用sqlite3的一定總結
從來沒有接觸過數據庫,因為最近的一個項目接觸了sqlite3,記錄一些經驗總結; 自學參考資料 sqlite3的相關文件,直接放在主程序的目錄內;(這樣不用額外設置路徑,項目小可以這樣做) 設置一下附加依賴項: 方式一:項目屬性—-鏈接器—-輸入—-添加”sqlite3.lib”(根據具體情況,有時需要添加附加庫目錄) 方式二:#prag...
sqlite3數據庫的基本操作
SQLite 的數據庫權限只依賴于文件系統,沒有用戶帳戶的概念。SQLite 有數據庫級鎖定,沒有網絡服務器。 它需要的內存,其它開銷很小,適合用于嵌入式設備。你需要做的僅僅是把它正確的編譯到你的程序。 接口由SQLite C API組成,也就是說不管是程序、腳本語言還是庫文件,最終都是通過它與SQLite交互的(我們通常用得較多的ODBC/JDBC最后也會轉化為相應C API的調用)。 SQLi...
猜你喜歡
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...