通过Valgrind运行时,SQLite为什么会存储不同的值
Why does sqlite store a different value when running through valgrind?
在我写的应用程序中,我注意到,当我尝试将双重插入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查找低级错误(内存泄漏等(,而不是进行任何功能测试。
另外,如果您要放入数据库中有八个特定字节,请执行此操作,而不是通过浮点往返。
- 为什么C中的通用链表中存储的数据已损坏
- 为什么我可以将变量存储在不是其最小对齐方式的倍数的地址?
- 为什么 bool 和 _Bool 如果它们在内存中占用 1 个字节,它们只能存储 0 或 1
- 为什么编译器不检查被覆盖函数的存储类?
- 为什么存储在数组中的地址值与其自己的地址相同,它应该指向某个不同的地址(&arr[0])?
- 为什么使用存储在虚拟方法表中的地址调用虚拟函数的函数会返回垃圾?
- 在C++中,如果成员引用在其声明中初始化,为什么需要存储空间?
- 为什么具有静态存储持续时间的同一内联变量在包含在 VS2017 编译的两个翻译单元中时会构造和销毁两次
- 为什么存储在内存位置的值会发生变化?
- 为什么std::atomic的默认构造函数不默认初始化底层存储值
- 为什么重复的空基存储不与 vtable 指针重叠?
- 如果char可以在C++中存储数字,为什么我们需要int
- 为什么使用 C++ 中的类对象写入文件中的数据以非文本格式存储?
- 为什么我的 std::set 不存储唯一值?
- 如果静态变量只为程序的整个部分存储了一个副本,为什么我不能使用静态变量交换 2 个数字?
- 为什么当我使用双精度时,Qt<->Matlab 正确写入和读取我的字节,但存储 uint32 的字节不正确?
- 为什么这段代码给出错误......'a'是指针到字符指针,应该存储's'的地址,因为s是指向数组的第一个元素的指针
- 为什么存储在变量中的值比原始答案少 1?
- 代码将向量存储为向量<vector>,为什么没有错误消息?
- 为什么 std::d eque 不允许指定存储桶大小?