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

C++自定义SQLite3类查询结果指针传递异常问题求助

问题根源及解决方案

核心问题:临时指针失效

你遇到的乱码是因为sqlite3_column_text()返回的指针指向SQLite内部的临时缓冲区,该缓冲区仅在当前sqlite3_step()调用期间、或sqlite3_stmt对象被sqlite3_finalize()销毁前有效。当db_query函数执行完毕回到main后,后续调用析构函数会执行sqlite3_finalize(stmt),此时缓冲区被释放,glb中保存的指针变为野指针,访问时自然出现乱码。

其他代码问题

  1. 手动调用析构函数错误:db->~dbase();属于违规操作,用new创建的对象必须用delete销毁,手动调用析构函数会导致内存泄漏,且对象内存无法被正确释放。
  2. 内存释放方式错误:delete glb;应改为delete[] glb;,因为glb是通过new[]分配的数组。
  3. 缺少SQLite操作错误检查:sqlite3_open、sqlite3_prepare_v2等函数的返回值未被校验,无法排查操作失败的情况。
  4. 内存泄漏:db_query中的pr数组未被释放,会造成内存泄漏。

修正后的代码

#include <sqlite3.h>
#include <iostream>
#include <cstring>

using namespace std;

class dbase
{
public:
    dbase(const char* dbfile);
    // 修改返回值,返回查询到的行数,方便main处理
    int db_query(unsigned char*** pas_t, const char* sql);
    virtual ~dbase();

private:
    sqlite3* db = nullptr;
    sqlite3_stmt* stmt = nullptr;
};

dbase::dbase(const char* dbfile)
{
    int rc = sqlite3_open(dbfile, &db);
    if (rc != SQLITE_OK) {
        cerr << "无法打开数据库: " << sqlite3_errmsg(db) << endl;
        sqlite3_close(db);
        db = nullptr;
    }
}

int dbase::db_query(unsigned char*** pas_t, const char* sql){
    if (!db) return -1;
    
    int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr);
    if (rc != SQLITE_OK) {
        cerr << "SQL准备失败: " << sqlite3_errmsg(db) << endl;
        return -1;
    }

    int row_count = 0;
    // 先统计行数,方便分配内存
    while (sqlite3_step(stmt) == SQLITE_ROW) {
        row_count++;
    }
    sqlite3_reset(stmt); // 重置stmt,重新遍历

    if (row_count == 0) {
        *pas_t = nullptr;
        return 0;
    }

    // 分配存储字符串指针的数组
    *pas_t = new unsigned char*[row_count + 1]; // 最后一个元素设为NULL作为结束标志
    int i = 0;
    while ((sqlite3_step(stmt)) == SQLITE_ROW) {
        const unsigned char* col_text = sqlite3_column_text(stmt, 0);
        if (col_text) {
            // 复制字符串到自己分配的内存
            (*pas_t)[i] = new unsigned char[strlen((const char*)col_text) + 1];
            strcpy((char*)(*pas_t)[i], (const char*)col_text);
        } else {
            (*pas_t)[i] = nullptr;
        }
        cout << "pas_t = " << (*pas_t)[i] << "  " << endl;
        i++;
    }
    (*pas_t)[row_count] = nullptr; // 设置结束标志
    cout << "\nend\n" << endl;

    sqlite3_finalize(stmt); // 提前finalize,避免后续析构重复操作
    stmt = nullptr;
    return row_count;
}

dbase::~dbase()
{
    if (stmt) {
        sqlite3_finalize(stmt);
    }
    if (db) {
        sqlite3_close(db);
    }
}

int main() {
    dbase* db = new dbase("./Test.db");
    unsigned char** glb = nullptr;
    int row_num = db->db_query(&glb, "SELECT Nome FROM Test");
    
    if (glb) {
        int n = 0;
        while(glb[n] != NULL){
            cout << "glb -- " << glb[n] << "--" << n << endl;
            // 释放每个字符串的内存
            delete[] glb[n];
            n++;
        }
        // 释放指针数组的内存
        delete[] glb;
    }

    // 正确销毁db对象
    delete db;
    return 0;
}

关键修改说明

  1. 复制字符串而非保存临时指针:用strcpy将sqlite3_column_text返回的内容复制到自主分配的堆内存中,确保字符串生命周期不受SQLite stmt的影响。
  2. 统计查询行数:先遍历一次stmt统计行数,再分配对应内存空间,避免固定数组大小的限制。
  3. 修正内存管理:用delete[]释放数组和字符串,用delete销毁db对象,避免内存泄漏。
  4. 添加错误处理:检查每个SQLite操作的返回值并打印错误信息,便于调试。
  5. 重置stmt:统计行数后用sqlite3_reset重置stmt,确保可以重新遍历查询结果。

内容的提问来源于stack exchange,提问作者Berico

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:45:08