SQLite3源码学习(19) printf的实现

SQLite中并没有使用标准库的printf()函数,而是自己实现了printf的全部功能并针对不同的应用做了一层封装。所有相关代码在printf.c里,下面就来分析SQLite是如何实现自己的printf

1.可变参数

       可变参数是实现printf的基础,其声明格式如下:

printf(const char *zFormat, ...)

在提取函数的参数时需要用到va_start,va_arg,va_end3个宏定义,一般编译都内置了这3个宏,不同的平台可能略微不同。

下面以win32平台的实现为例,栈的地址是从高地址到低地址扩展,而参数的地址入栈顺序是从右向左,所以zFormat参数的地址最小,后面参数的地址依次增大。

clip_image001

那么,知道第一个固定参数的地址之后,后面的参数地址也就确定了。

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的所有对外接口如下图所示:

clip_image003

sqlite3DebugPrintfprintf的功能完全一样,都是把打印信息输出到stdout

sqlite3_mprintf并没有输出到stdout,而是返回最后的结果的指针。

sqlite3_snprintf功能和snprintf相同,把结果保存到输入参数的目标地址zBuf里。

sqlite3VMPrintf相比上面几个接口还支持内部%格式转换的扩展。

sqlite3_log用来输出错误信息,需要用户先注册回调函数,把结果传入回调函数。

sqlite3XPrintf就是sqlite3VXPrintf加上va_startva_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 accacc主要用来保存格式转换后的字符串内容和长度,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个字符cp->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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值