【问题标题】:How does one debug sqlite3_prepare_v2 and sqlite3_step not giving expected values when no error codes are generated?当没有生成错误代码时,如何调试 sqlite3_prepare_v2 和 sqlite3_step 没有给出预期值?
【发布时间】:2020-08-05 17:18:52
【问题描述】:

我在虚幻引擎 4 的上下文中使用 sqlite3 C/C++ 合并时遇到问题...

我可以打开与数据库的连接,将数据写入其中,使用 DB Browser 验证这一点,但使用 sqlite3_prepare_v2sqlite3_step 运行 SELECT 语句会出现意外行为。我正在检查返回值并没有看到任何错误代码,但是当我希望看到sqlite3_step 返回的SQLITE_ROW 时,我看到了SQLITE_DONE。如果我注销 sqlite3_expanded_sql 的结果,我会得到正确插入所有参数的预期语句,如果我将其复制并粘贴到 DB Browser 中,我会得到预期的结果。

要提取的数据特定于程序的每次运行,因此我知道我没有在 DB Browser 中看到旧的或不相关的数据,并且我知道我的“写入”函数也按预期运行。

特别奇怪的是,在注释掉所有对SQLPreparedStatementString 的引用(我在尝试调试问题时使用它)之后,代码的执行方式有所不同:我现在确实得到了一行(而不是预期的 4)。

这几乎让我怀疑存在内存问题,也许 sqlite3 和 Unreal 都在写入相同的内存部分而没有相互通信?我倾向于依赖 UE4 的内存管理,并且在 UE4 之外没有很多 C/C++ 经验,所以我对 malloc 之类的知识几乎仅限于“这里是龙”等一般感觉。我也知道sqlite3_prepare_v2sqlite3_step 是往往会进行大量内存分配的函数,但如果这是导致问题的原因,我很惊讶它很少发生,因为我成功地调用了它们几个每次游戏运行一千次(添加数据)。

我知道可以为 sqlite 设置编译时选项,为它预先分配一个内存块,以便它在运行时根本不使用 malloc,但我还没有完全弄清楚如何在在 UE4 中包含合并的上下文。

相关代码如下:

TArray<FName> UOzyBrainLogger::GetLoggedCivsForTurn(const int32 TurnNumber)
{
    TArray<FName> rVal;
    
        sqlite3_stmt *SQLStatement;
        FString SQLCommand = "SELECT DISTINCT CivName FROM Decision WHERE (GameID = ? AND TurnNumber = ?) ;";
        int rc;
        
        rc = sqlite3_prepare_v2(DB, TCHAR_TO_UTF8(*SQLCommand), -1, &SQLStatement, NULL);
        if (rc == SQLITE_OK)
        {
            //FString SQLPreparedStatementString;
            UE_LOG(OzyBrainLogger, Log, TEXT("Getting Civs logged for turn %d in game %s"), TurnNumber, *GameID);
            if(sqlite3_bind_text(  SQLStatement, 1, TCHAR_TO_UTF8(*GameID), -1, SQLITE_STATIC) == SQLITE_OK &&
               sqlite3_bind_int(   SQLStatement, 2, TurnNumber)                                == SQLITE_OK)
            {
                //SQLPreparedStatementString = UTF8_TO_TCHAR(sqlite3_expanded_sql(SQLStatement));
                //UE_LOG(OzyBrainLogger, Log, TEXT("Successfully created statement: %s"), *SQLPreparedStatementString);
                rc = sqlite3_step(SQLStatement);
                if (rc == SQLITE_DONE)
                {
                    UE_LOG(OzyBrainLogger, Log, TEXT("No results found?"));
                }
                else if (rc != SQLITE_ROW)
                {
                    UE_LOG(OzyBrainLogger, Log, TEXT("Return code was: %d"), rc);
                }
                while (rc == SQLITE_ROW)
                {
                    FName CivName = FName(*FString(UTF8_TO_TCHAR((const char*) sqlite3_column_text(SQLStatement, 0))));
                    //UE_LOG(OzyBrainLogger, Log, TEXT("Returning %s for turn %d via statement: %s"), *CivName.ToString(), TurnNumber, *SQLPreparedStatementString);
                    UE_LOG(OzyBrainLogger, Log, TEXT("Returning %s for turn %d"), *CivName.ToString(), TurnNumber);
                    
                    rVal.Add(CivName);
                    rc = sqlite3_step(SQLStatement);
                }
            }
            else
            {
                UE_LOG(OzyBrainLogger, Warning, TEXT("%s"), UTF8_TO_TCHAR(sqlite3_errmsg(DB)));
            }
        }
        else
        {
            UE_LOG(OzyBrainLogger, Warning, TEXT("%s"), UTF8_TO_TCHAR(sqlite3_errmsg(DB)));
        }
        sqlite3_finalize(SQLStatement);
    
    return rVal;
}

我可能遗漏了一些非常明显的东西,但我不确定如何进一步调查以取得进展,因此任何关于探索途径的建议都会非常有帮助。

感觉有点失落!

【问题讨论】:

  • 我建议两件事:(1)将代码重写为“向左推”:而不是if/then/else,更像:如果错误则返回。这往往使代码更容易理解和推理。 (2) 检查并对所有错误代码做出反应,例如有时代码会记录并继续运行,尽管出现了错误。另一个例子是调用 sqlite3_finalize(),缺少错误检查。
  • 啊,关于sqlite3_finalize 的错误检查的要点。不过似乎什么都抓不到。我最初避免了“如果失败则返回”模式,以减少创建一个没有自行清理的返回分支的机会。正如你所描述的那样“向左移动”,我总是愿意听取关于最佳实践等的看法。
  • 我正在尝试使用sqlite3_config(SQLITE_CONFIG_HEAP, SQLiteDBMemoryHeap, 2^25, 64);,其中SQLiteDBMemoryHeap 是通过FMemory::Malloc(2^25, 8); 分配的,但是当我尝试打开数据库时会得到SQLITE_NOMEM 返回码。数据库目前在磁盘上只有大约 57kb,所以我很惊讶它说它没有足够的内存。有什么东西阻止 sqlite 使用 UE4 函数分配的内存吗?我认为至少如果我使用 FMemory,它会确保至少一个人会知道另一个人......
  • 2^25 是 27 字节......这太小了,无法使用内存缓冲区。
  • 我不熟悉你正在使用的这个框架,但是使用 SQLITE_STATICTCHAR_TO_UTF8() 闻起来很有趣。

标签: c++ sqlite unreal-engine4


【解决方案1】:

我想我设法解决了它!问题似乎是以下几个方面的组合:

  1. 在我的对象的构造函数中调用 sqlite3_open 期望在 UE4 不时静默调用构造函数时(例如在编译时)出现类似单例的行为。
  2. 使用SQLITE_STATIC 代替SQLITE_TRANSIENT
  3. 添加返回码 (rc) 绝对检查任何地方

解决这两个问题似乎已经解决了主要问题。

最后一个问题(只返回 2 行而不是 4 行)是一个更普通的新手错误(我将对 sqlite3_step 的调用重新安排到 do ... while 循环中,结果意外地每次迭代调用 sqlite3_step 两次!

没有解决主要问题的事情包括使用SQLITE_CONFIG_MEMSTATUSSQLITE_CONFIG_LOOKASIDESQLITE_CONFIG_HEAP 调用sqlite3_config然而:当我开始检查这些调用的返回码时正确地发现错误,这是意识到某些东西已经开始数据库连接而我没有意识到的主要线索(即 UE4 的静默构造函数调用编译)

【讨论】:

  • 感谢您坚持不懈并花时间在这里报告问题所在。
  • 如果您想从这次经历中记住一件事:始终检查错误代码。表面上看起来很无聊或杂乱无章,但它可以节省你的培根。
猜你喜欢
  • 2023-03-07
  • 2011-04-07
  • 1970-01-01
  • 1970-01-01
  • 2017-02-16
  • 2017-08-05
  • 2022-10-07
  • 1970-01-01
  • 2019-01-12
相关资源
最近更新 更多