C++实现SQLite文件到内存数据库备份时触发Segmentation Fault
问题描述
我想用C++将文件系统中的SQLite数据库备份到内存数据库,参考SQLite官方文档示例编写代码,编译正常但运行时出现Segmentation Fault错误,最后一行printf("foo1");未打印,代码如下:
#include <stdio.h> #include <sqlite3.h> #include <nlohmann/json.hpp> using namespace std; using json = nlohmann::json; static int callback(void *NotUsed, int count, char **data, char **column) { int i; int lastEntry = count - 1; printf("{"); for (i=0; i<count; i++) { if (i == lastEntry ) { printf("\"%s\" : \"%s\"", column[i], data[i] ? data[i] : "NULL"); } else { printf("\"%s\" : \"%s\",", column[i], data[i] ? data[i] : "NULL"); } } printf("}\n"); return 0; } int loadOrSaveDb(sqlite3 *pFile, const char *zFilename, int isSave){ int rc; /* Function return code */ sqlite3 *pInMemory; /* Database connection opened on zFilename */ sqlite3_backup *pBackup; /* Backup object used to copy data */ sqlite3 *pTo; /* Database to copy to (pFile or pInMemory) */ sqlite3 *pFrom; /* Database to copy from (pFile or pInMemory) */ /* Open the database file identified by zFilename. Exit early if this fails ** for any reason. */ rc = sqlite3_open(zFilename, &pFile); if( rc==SQLITE_OK ){ pFrom = (isSave ? pInMemory : pFile); pTo = (isSave ? pFile : pInMemory); pBackup = sqlite3_backup_init(pTo, "main", pFrom, "main"); if( pBackup ){ (void)sqlite3_backup_step(pBackup, -1); (void)sqlite3_backup_finish(pBackup); } printf("foo"); rc = sqlite3_errcode(pTo); } /* Close the database connection opened on database file zFilename ** and return the result of this function. */ (void)sqlite3_close(pFile); return rc; } int main(int argc, char* argv[]) { sqlite3 *db; char *zErrMsg = 0; int rc; string sql; /* Open Database */ rc = sqlite3_open("test.db", &db); if( rc ) { fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); return(0); } else { fprintf(stderr, "Opened database successfully\n"); } /* Create SQL Statement */ sql = "CREATE TABLE COMPANY(" \ "ID INT PRIMARY KEY NOT NULL," \ "name TEXT NOT NULL," \ "AGE INT NOT NULL," \ "ADDRESS CHAR(50)," \ "SALARY REAL );"; /* Execute SQL statement */ rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg); if( rc != SQLITE_OK ){ fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } else { fprintf(stdout, "Table created successfully\n"); } /* Insert SQL statement */ sql = "INSERT INTO COMPANY " \ "VALUES (1, 'Paul', 32, 'California', 20000.00 ); " \ "INSERT INTO COMPANY " \ "VALUES (2, 'Allen', 25, 'Texas', 15000.00 ); " \ "INSERT INTO COMPANY " \ "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );" \ "INSERT INTO COMPANY " \ "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );"; /* Execute SQL statement */ rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg); if( rc != SQLITE_OK ){ fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } else { fprintf(stdout, "Records created successfully\n"); } /* Select SQL statement */ //sql = "SELECT * from COMPANY WHERE NAME LIKE 'Paul'"; sql = "SELECT * from COMPANY"; /* Execute SQL statement */ rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg); if( rc != SQLITE_OK ) { fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } else { fprintf(stdout, "Operation done successfully\n"); } loadOrSaveDb(db, "test.db", 0); sqlite3_close(db); printf("foo1"); return 0; }
错误原因分析
- 野指针访问:
loadOrSaveDb函数中pInMemory未初始化,属于野指针。当isSave=0(备份到内存)时,pTo被赋值为该野指针,调用sqlite3_backup_init时传入无效指针直接触发段错误。 - 数据库连接逻辑混乱:
main函数已经打开了test.db,但loadOrSaveDb中再次调用sqlite3_open打开同一文件,且pFile是传值参数,修改的是局部副本;函数最后关闭的是新打开的连接,而非传入的db,后续main中再次关闭db可能导致无效指针访问。 - 备份流程错误:备份到内存库的正确流程是先创建内存数据库连接,再从已打开的文件数据库备份数据,而非重新打开文件数据库。
修复后的代码
#include <stdio.h> #include <sqlite3.h> #include <nlohmann/json.hpp> using namespace std; using json = nlohmann::json; static int callback(void *NotUsed, int count, char **data, char **column) { int i; int lastEntry = count - 1; printf("{"); for (i=0; i<count; i++) { if (i == lastEntry ) { printf("\"%s\" : \"%s\"", column[i], data[i] ? data[i] : "NULL"); } else { printf("\"%s\" : \"%s\",", column[i], data[i] ? data[i] : "NULL"); } } printf("}\n"); return 0; } // 将文件数据库备份到内存,返回内存数据库连接指针 sqlite3* loadDbToMemory(sqlite3 *pFileDb) { int rc; sqlite3 *pInMemory = nullptr; sqlite3_backup *pBackup = nullptr; // 创建内存数据库连接 rc = sqlite3_open(":memory:", &pInMemory); if (rc != SQLITE_OK) { fprintf(stderr, "Failed to open in-memory database: %s\n", sqlite3_errmsg(pInMemory)); sqlite3_close(pInMemory); return nullptr; } // 初始化备份:从文件库到内存库 pBackup = sqlite3_backup_init(pInMemory, "main", pFileDb, "main"); if (pBackup) { // 执行完整备份 rc = sqlite3_backup_step(pBackup, -1); if (rc != SQLITE_DONE && rc != SQLITE_OK) { fprintf(stderr, "Backup step failed: %s\n", sqlite3_errmsg(pInMemory)); } sqlite3_backup_finish(pBackup); } else { fprintf(stderr, "Backup init failed: %s\n", sqlite3_errmsg(pInMemory)); sqlite3_close(pInMemory); return nullptr; } printf("foo\n"); return pInMemory; } int main(int argc, char* argv[]) { sqlite3 *db; sqlite3 *memDb = nullptr; char *zErrMsg = 0; int rc; string sql; /* Open Database */ rc = sqlite3_open("test.db", &db); if( rc ) { fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); return(0); } else { fprintf(stderr, "Opened database successfully\n"); } /* Create SQL Statement */ sql = "CREATE TABLE COMPANY(" \ "ID INT PRIMARY KEY NOT NULL," \ "name TEXT NOT NULL," \ "AGE INT NOT NULL," \ "ADDRESS CHAR(50)," \ "SALARY REAL );"; /* Execute SQL statement */ rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg); if( rc != SQLITE_OK ){ fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } else { fprintf(stdout, "Table created successfully\n"); } /* Insert SQL statement */ sql = "INSERT INTO COMPANY " \ "VALUES (1, 'Paul', 32, 'California', 20000.00 ); " \ "INSERT INTO COMPANY " \ "VALUES (2, 'Allen', 25, 'Texas', 15000.00 ); " \ "INSERT INTO COMPANY " \ "VALUES (3, 'Teddy', 23, 'Norway', 20000.00 );" \ "INSERT INTO COMPANY " \ "VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 );"; /* Execute SQL statement */ rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg); if( rc != SQLITE_OK ){ fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } else { fprintf(stdout, "Records created successfully\n"); } /* Select SQL statement */ sql = "SELECT * from COMPANY"; rc = sqlite3_exec(db, sql.c_str(), callback, 0, &zErrMsg); if( rc != SQLITE_OK ) { fprintf(stderr, "SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } else { fprintf(stdout, "Operation done successfully\n"); } // 备份到内存数据库 memDb = loadDbToMemory(db); if (memDb) { printf("Successfully backed up to in-memory database\n"); // 测试内存数据库查询 sql = "SELECT * from COMPANY"; rc = sqlite3_exec(memDb, sql.c_str(), callback, 0, &zErrMsg); if( rc != SQLITE_OK ) { fprintf(stderr, "Mem DB SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } sqlite3_close(memDb); // 关闭内存数据库 } sqlite3_close(db); printf("foo1\n"); return 0; }
关键修改点
- 重命名函数为
loadDbToMemory,明确功能为将文件数据库备份到内存,返回内存库连接指针。 - 正确创建内存数据库:调用
sqlite3_open(":memory:", &pInMemory)初始化内存库连接。 - 修复备份方向:
sqlite3_backup_init参数设置为从传入的文件库备份到内存库。 - 移除重复的文件库打开操作,避免连接混乱;由
main统一管理文件库的关闭。 - 添加内存库的测试代码,验证备份结果。
- 修复后
printf("foo1")可正常打印,段错误问题解决。
内容的提问来源于stack exchange,提问作者naomikim
相关产品推荐
相关产品推荐

