C语言 sqlite3内存泄漏(内存占用不断增长)的解决方法

问题

最近使用 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_USEDMALLOC_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 释放,当然,我们自己也可以通过 nRownColumn 进行遍历做 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

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值