在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。