You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
}
错误原因分析
  1. 野指针访问:loadOrSaveDb函数中pInMemory未初始化,属于野指针。当isSave=0(备份到内存)时,pTo被赋值为该野指针,调用sqlite3_backup_init时传入无效指针直接触发段错误。
  2. 数据库连接逻辑混乱:main函数已经打开了test.db,但loadOrSaveDb中再次调用sqlite3_open打开同一文件,且pFile是传值参数,修改的是局部副本;函数最后关闭的是新打开的连接,而非传入的db,后续main中再次关闭db可能导致无效指针访问。
  3. 备份流程错误:备份到内存库的正确流程是先创建内存数据库连接,再从已打开的文件数据库备份数据,而非重新打开文件数据库。
修复后的代码
#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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 21:54:15