通过Valgrind运行时,SQLite为什么会存储不同的值

Why does sqlite store a different value when running through valgrind?

本文关键字:存储 为什么 Valgrind 运行时 SQLite 通过      更新时间:2023-10-16

在我写的应用程序中,我注意到,当我尝试将双重插入sqlite数据库中时,实际上存储了一个(稍有(的值,但只有当应用程序运行运行时通过Valgrind。直接调用程序(不进行重新编译(时,所有双打都是按预期存储的。

此代码重现问题。

删除了一些安全检查等。
#include <sqlite3.h>
#include <iostream>
#include <vector>
#include <any>
#include <memory>
#include <cstring>
#include <iomanip>
class SqliteDB
{
  sqlite3 *d_db;
  bool d_ok;
 public:
  inline SqliteDB(std::string const &name);
  inline ~SqliteDB();
  inline void exec(std::string const &q, std::vector<std::vector<std::pair<std::string, std::any>>> *results);
};
inline SqliteDB::SqliteDB(std::string const &name)
  :
  d_db(nullptr),
  d_ok(false)
{
  d_ok = (sqlite3_open(name.c_str(), &d_db) == 0);
}
inline SqliteDB::~SqliteDB()
{
  if (d_ok)
    sqlite3_close(d_db);
}
inline void SqliteDB::exec(std::string const &q, std::vector<std::vector<std::pair<std::string, std::any>>> *results)
{
  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(d_db, q.c_str(), -1, &stmt, nullptr) != SQLITE_OK)
  {
    std::cout << "SQL Error: " << sqlite3_errmsg(d_db) << std::endl;
    return;
  }
  int rc;
  results->clear();
  while ((rc = sqlite3_step(stmt)) == SQLITE_ROW)
  {
    results->resize(results->size() + 1);
    for (int i = 0; i < sqlite3_column_count(stmt); ++i)
    {
      if (sqlite3_column_type(stmt, i) == SQLITE_INTEGER)
      {
        results->back().emplace_back(std::make_pair(sqlite3_column_name(stmt, i), sqlite3_column_int64(stmt, i)));
      }
      else if (sqlite3_column_type(stmt, i) == SQLITE_FLOAT)
      {
        results->back().emplace_back(std::make_pair(sqlite3_column_name(stmt, i), sqlite3_column_double(stmt, i)));
      }
      else if (sqlite3_column_type(stmt, i) == SQLITE_TEXT)
      {
        results->back().emplace_back(std::make_pair(sqlite3_column_name(stmt, i), std::string(reinterpret_cast<char const *>(sqlite3_column_text(stmt, i)))));
      }
      else if (sqlite3_column_type(stmt, i) == SQLITE_BLOB)
      {
        size_t blobsize = sqlite3_column_bytes(stmt, i);
        std::shared_ptr<unsigned char []> blob(new unsigned char[blobsize]);
        std::memcpy(blob.get(), reinterpret_cast<unsigned char const *>(sqlite3_column_blob(stmt, i)), blobsize);
        results->back().emplace_back(std::make_pair(sqlite3_column_name(stmt, i), std::make_pair(blob, blobsize)));
      }
      else if (sqlite3_column_type(stmt, i) == SQLITE_NULL)
      {
        results->back().emplace_back(std::make_pair(sqlite3_column_name(stmt, i), nullptr));
      }
    }
  }
  if (rc != SQLITE_DONE)
    std::cout << "SQL Error: " << sqlite3_errmsg(d_db) << std::endl;
  sqlite3_finalize(stmt);
}
inline std::string toHexString(double d)
{
  unsigned char *data = reinterpret_cast<unsigned char *>(&d);
  std::ostringstream oss;
  oss << "(hex:) ";
  for (uint i = 0; i < sizeof(d); ++i)
    oss << std::hex << std::setfill('0') << std::setw(2)
        << (static_cast<int32_t>(data[i]) & 0xFF)
        << ((i == sizeof(d) - 1) ? "" : " ");
  return oss.str();
}
int main()
{
  SqliteDB db(":memory:");
  std::vector<std::vector<std::pair<std::string, std::any>>> results;
  db.exec("CREATE TABLE part (_id INTEGER PRIMARY KEY, ratio REAL)", &results);
  double d = 1.4814814329147339;
  std::cout << "Inserting into table: " << std::defaultfloat << std::setprecision(17) << d
            << " " << toHexString(d) << std::endl;
  db.exec("INSERT INTO part VALUES (1,1.4814814329147339)", &results);
  db.exec("SELECT ratio FROM part WHERE _id = 1", &results);
  for (uint i = 0; i < results.size(); ++i)
    for (uint j = 0; j < results[i].size(); ++j)
    {
      if (results[i][j].second.type() == typeid(double))
        std::cout << "Retrieved from table: " << std::defaultfloat << std::setprecision(17) << std::any_cast<double>(results[i][j].second)
                  << " " << toHexString(std::any_cast<double>(results[i][j].second)) << std::endl;
    }
  return 0;
}

我已经验证了问题在存储值时而不是检索值时发生的问题(即数据库实际上包含不同的双重(。上述程序的输出:

[~/valgrindsqlitedouble] $ g++ -std=c++2a -Wall -Wextra -Wshadow -Wold-style-cast -pedantic -fomit-frame-pointer -O1 -g -lsqlite3 main.cc
[~/valgrindsqlitedouble] $ ./a.out 
Inserting into table: 1.4814814329147339 (hex:) 00 00 00 e0 25 b4 f7 3f
Retrieved from table: 1.4814814329147339 (hex:) 00 00 00 e0 25 b4 f7 3f
[~/valgrindsqlitedouble] $ valgrind ./a.out 
==3340== Memcheck, a memory error detector
==3340== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==3340== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==3340== Command: ./a.out
==3340== 
Inserting into table: 1.4814814329147339 (hex:) 00 00 00 e0 25 b4 f7 3f
Retrieved from table: 1.4814814329147341 (hex:) 01 00 00 e0 25 b4 f7 3f
==3340== 
==3340== HEAP SUMMARY:
==3340==     in use at exit: 0 bytes in 0 blocks
==3340==   total heap usage: 299 allocs, 299 frees, 269,972 bytes allocated
==3340== 
==3340== All heap blocks were freed -- no leaks are possible
==3340== 
==3340== For counts of detected and suppressed errors, rerun with: -v
==3340== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
[~/valgrindsqlitedouble] $

我假设当SQLite将字符串转换为double时会发生一些舍入错误,但是任何人都可以解释为什么只有在Valgrind运行时才发生?

第二,我可以摆脱这个问题吗?在最终程序中,精度甚至并不重要,但是在开发过程中,拥有可预测的产出将是一件好事。我正在处理一些大二进制文件,并测试验证程序正常工作的最简单方法是将生成的输出文件(包括数据库(与已知的好数据进行比较。有什么办法可以让Sqlite插入我想要的8个字节?

谢谢!

根据文档:

然后,您的程序将在Valgrind Core提供的合成CPU上运行。

因此,在纸上,在Valgrind下运行时,程序的行为有很大的范围。当然,在实践中,我们希望情况并非如此,因为我们的程序不仅应该是便携式的,而且如果Valgrind改变了他们的行为,那么即使在同一平台上,我们也不会真正测试我们想要的测试。<<<<<<<</p>

但是,您的期望已经不可出现,开发人员将其用作限制中的辩护

从3.0.0版本开始,Valgrind在其实现X86/AMD64浮点相对于IEEE754的实现时具有以下限制。

精度:没有支持80位算术的支持。在内部,valgrind代表64位的所有"长双"数字,因此结果可能存在一些差异。这是否至关重要。请注意,使用转换为64位/从64位的转换,可以正确模拟X86/AMD64 FLDT/FSTPT指令(读取/写入80位编号(,以便80位数字的内存图像看起来正确,如果有人想看到。

从许多FP回归测试中观察到的印象是准确性差异并不显着。一般来说,如果一个程序依赖于80位精度,则可能会将其移植到非X86/AMD64平台上,该平台仅支持64位fp Precision。即使在x86/amd64上,该程序可能会获得不同的结果取决于是否使用SSE2说明(仅64位(或X87指令(80位(。网络效果是使FP程序表现得好像是在具有64位IEEE Floats的机器上运行的,例如PowerPC。从FP的角度来看,比X86更像powerpc,而与x86相比,明显的精度差异要少得多。

截至版本3.0.0,Valgrind在其实现X86/AMD64 SSE2 FP算术时具有以下限制,相对于IEEE754。

本质上是相同的:无例外,并且对圆形模式的观察有限。同样,SSE2具有控制位,使其将其视为零(DAZ(和相关作用,即齐平否至零(FTZ(。这两个导致SSE2算术比IEEE所需的准确。 valgrind检测,忽略并可以警告,尝试启用任何一种模式。

et al。

我建议使用Valgrind查找低级错误(内存泄漏等(,而不是进行任何功能测试。

另外,如果您要放入数据库中有八个特定字节,请执行此操作,而不是通过浮点往返。

相关文章: