问题
最近使用 sqlite3 库的时候,内存一直不断增长,查阅资料和源码之后才发现自己对 sqlite3 的了解过于浅薄,没有用好。
Sqlite3 的内存分配
sqlite3 在内存分配(malloc
)的时候,会将每一次 malloc
的大小累加到 sqlite3 的全局变量中。相反的,在释放内存之后,也要从全局变量中减掉相应的内存。
因此,在 malloc
的时候,sqlite3 会把分配的内存头地址与分配大小进行记录,如果我们直接使用 free
进行回收,则仅仅只是回收了分配的内存,却没法回收那份记录,同时也无法将 sqlite3 中的全局内存大小减去。
以下 sqlite3 内存释放的源码:
SQLITE_API void sqlite3_free(void *p){
if( p==0 ) return; /* IMP: R-49053-54554 */
assert( sqlite3MemdebugHasType(p, MEMTYPE_HEAP) );
assert( sqlite3MemdebugNoType(p, (u8)~MEMTYPE_HEAP) );
if( sqlite3GlobalConfig.bMemstat ){
sqlite3_mutex_enter(mem0.mutex);
sqlite3StatusDown(SQLITE_STATUS_MEMORY_USED, sqlite3MallocSize(p));
sqlite3StatusDown(SQLITE_STATUS_MALLOC_COUNT, 1);
sqlite3GlobalConfig.m.xFree(p);
sqlite3_mutex_leave(mem0.mutex);
}else{
sqlite3GlobalConfig.m.xFree(p);
}
}
可以很明显的看到 sqlite3GlobalConfig.bMemstat
如果有效的话(默认就是 1), 会减去 MEMORY_USED
和 MALLOC_COUNT
,而 sqlite3MallocSize(p)
能通过 指针 p
得到已分配内存的大小,说明在分配内存的时候,就已经做了映射记录,而这份记录也一定会在 sqlite3GlobalConfig.m.xFree
方法中一并释放。
坑点
一般我们网上看到的教程,类似下面的案例。
int slt_saveOne(char *oid, char *value) {
char *sql, *err;
int rc;
sql = sqlite3_mprintf("update main.pdu set value = '%s' where oid = '%s'", value, oid);
rc = sqlite3_exec(db, sql, NULL, NULL, &err);
sqlite3_free(err);
return rc == SQLITE_OK;
}
可以看到 这里仅仅把 err
释放掉了, 而没有将 sql
释放,而我们很有可能会觉 sqlite3_mprintf
函数返回的仅仅是一串字符,理论上只需使用 free(sql)
即可回收。
但事实上这样是不行的,只要使用了 sqlite3
的方法,并分配了内存,那么一定有一份分配内存的记录,必须将这份记录也一起释放,所以 sqlite3
源码官方就有说明,必须使用 sqlite3_free
来释放。
下面是完整的正确的使用方法:
int slt_saveOne(char *oid, char *value) {
char *sql, *err;
int rc;
// 使用 sqlite3 开辟的内存必须使用 sqlite3_free 回收
sql = sqlite3_mprintf("update main.pdu set value = '%s' where oid = '%s'", value, oid);
rc = sqlite3_exec(db, sql, NULL, NULL, &err);
sqlite3_free(err);
sqlite3_free(sql);
return rc == SQLITE_OK;
}
当然也可以不使用 sqlite3_mprintf
方法来生成 sql,直接使用 sprintf
即可:
int slt_saveOne(char *oid, char *value) {
char *sql, *err;
int rc;
// 使用 sprintf 则无需对 sql 进行回收
sprintf(sql, "update main.pdu set value = '%s' where oid = '%s'", value, oid);
rc = sqlite3_exec(db, sql, NULL, NULL, &err);
sqlite3_free(err);
return rc == SQLITE_OK;
}
要是 select
语句,我们还需要用到 res
结果集的,也需要将 res
回收;
但因为是二维字符串数组(三维char数组),所以需要 sqlite3_free_table
函数做遍历回收。
例如:
int slt_saveOne(char *oid, char *value) {
char *sql, *err;
char **res;
int rc, nRow, nColumn;
// 使用 sprintf 则无需对 sql 进行回收
sprintf(sql, "update main.pdu set value = '%s' where oid = '%s'", value, oid);
rc = sqlite3_get_table(db, sql, &res, &nRow, &nColumn, &err);
sqlite3_free_table(res);
sqlite3_free(err);
return rc == SQLITE_OK;
}
下面是 sqlite3_free_table
的源码
/*
** This routine frees the space the sqlite3_get_table() malloced.
*/
SQLITE_API void sqlite3_free_table(
char **azResult /* Result returned from sqlite3_get_table() */
){
if( azResult ){
int i, n;
azResult--;
assert( azResult!=0 );
n = SQLITE_PTR_TO_INT(azResult[0]);
for(i=1; i<n; i++){ if( azResult[i] ) sqlite3_free(azResult[i]); }
sqlite3_free(azResult);
}
}
通过源码,我们可以知道,其实他只是遍历 res
结果集,并做 sqlite3_free
释放,当然,我们自己也可以通过 nRow
和 nColumn
进行遍历做 sqlite3_free
释放,效果是一样的。
总结
凡事 sqlite3
分配的内存,记得都可以尝试使用 sqlite3_free
来释放, 如果遇到类似 sqlite3
内存一直上涨,可以从它的内存分配机制,和 free
机制入手检查。
总之就是,把所有的 char*
使用 sqlite3_free
释放 ,把所有的 char**
数组,使用 sqlite3_free_table
来释放;
当然最佳方法,就是通过查看源码方法,来得到注意事项;
比如我们需要调用 sqlite3_get_table
方法,那么看下源码就知道要怎么做了:
/*
** Query the database. But instead of invoking a callback for each row,
** malloc() for space to hold the result and return the entire results
** at the conclusion of the call.
**
** The result that is written to ***pazResult is held in memory obtained
** from malloc(). But the caller cannot free this memory directly.
** Instead, the entire table should be passed to sqlite3_free_table() when
** the calling procedure is finished using it.
*/
SQLITE_API int sqlite3_get_table(
sqlite3 *db, /* The database on which the SQL executes */
const char *zSql, /* The SQL to be executed */
char ***pazResult, /* Write the result table here */
int *pnRow, /* Write the number of rows in the result here */
int *pnColumn, /* Write the number of columns of result here */
char **pzErrMsg /* Write error messages here */
)
源码中是不是 写的很清楚 T_T