Invalid free() / delete / delete[] / realloc() for fclose()?
Invalid free() / delete / delete[] / realloc() for fclose()?
我尝试在Linux64上运行/编译OpenTibia Server。稍作调整,编译,一切似乎都很好。然而,Valgrind说:
==32360== Invalid free() / delete / delete[] / realloc()
==32360== at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==32360== by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
==32360== by 0x41CF8D: FileLoader::~FileLoader() (fileloader.cpp:49)
==32360== by 0x45DB1B: Items::loadFromOtb(std::string) (itemloader.h:232)
==32360== by 0x4067D7: main (otserv.cpp:564)
==32360== Address 0x8126590 is 0 bytes inside a block of size 568 free'd
==32360== at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==32360== by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
==32360== by 0x41D268: FileLoader::openFile(char const*, bool, bool) (fileloader.cpp:92)
==32360== by 0x45DB00: Items::loadFromOtb(std::string) (items.cpp:230)
==32360== by 0x4067D7: main (otserv.cpp:564)
现在,对于FileLoader(尤其是析构函数),代码如下:
/*somewhere in the header*/
FILE* m_file;
FileLoader::FileLoader() {
m_file = NULL;
m_buffer = new unsigned char[1024];
//cache, some cache data
memset(m_cached_data, 0, sizeof(m_cached_data));
}
FileLoader::~FileLoader() {
if(m_file){
fclose(m_file);
m_file = NULL;
}
delete[] m_buffer;
for(int i = 0; i < CACHE_BLOCKS; i++){
if(m_cached_data[i].data)
delete m_cached_data[i].data;
}
}
bool FileLoader::openFile(const char* filename, bool write, bool caching /*= false*/){
if(write) {/*unimportant*/}
else {
unsigned long version;
m_file = fopen(filename, "rb");
if(m_file){
fread(&version, sizeof(unsigned long), 1, m_file);
if(version > 0){/*version is 0*/}
else{
if(caching){
m_use_cache = true;
fseek(m_file, 0, SEEK_END);
int file_size = ftell(m_file);
m_cache_size = min(32768, max(file_size/20, 8192)) & ~0x1FFF;
}
return true;
}
}
else{
m_lastError = ERROR_CAN_NOT_OPEN;
return false;
}
}
}
ItemLoader只是FileLoader的扩展:
class ItemLoader : public FileLoader {/*Overrides nothing*/};
现在进入Items中的功能:
int Items::loadFromOtb(std::string file) {
ItemLoader f;
if(!f.openFile(file.c_str(), false, true)){return f.getError();}
//...Loading, processing, reading from file and stuff...
//delete &f; //I tried this but didn't change anything
return ERROR_NONE;
}
问题是,Valgrind是指向fclose的问题还是其他问题?还要注意,这个应用程序使用libboost(如果它有什么作用的话)。我试图尽可能具体
Vagrind直接显示您的问题-您在同一个FILE
描述符上调用fclose
两次:
==32360== Invalid free() / delete / delete[] / realloc()
==32360== at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
second call ==32360== by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
--------->> ==32360== by 0x41CF8D: FileLoader::~FileLoader() (fileloader.cpp:49)
==32360== by 0x45DB1B: Items::loadFromOtb(std::string) (itemloader.h:232)
==32360== by 0x4067D7: main (otserv.cpp:564)
==32360== Address 0x8126590 is 0 bytes inside a block of size 568 free'd
==32360== at 0x4C2BDEC: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
first call ==32360== by 0x6074AE4: fclose@@GLIBC_2.2.5 (iofclose.c:85)
--------->> ==32360== by 0x41D268: FileLoader::openFile(char const*, bool, bool) (fileloader.cpp:92)
==32360== by 0x45DB00: Items::loadFromOtb(std::string) (items.cpp:230)
==32360== by 0x4067D7: main (otserv.cpp:564)
第二次调用在第49行析构函数中,第一次调用在第92行openFile中。
看起来FileLoader
存在做一种形式的缓存/缓冲。当使用FILE*
或IOStreams时,不需要自己做缓冲,它们会为您做。FileLoader
在此基础上添加了另一个缓冲区
你可能想重构FileLoader
,删除所有缓冲,只为你的类提供序列化功能,而将所有I/O和相关缓冲委托给FILE*
或IOStreams。
房间里的大象:啊。这是c++。为什么使用FILE?Fstream会帮你处理这些废话(嗯…其中一些)不会掉到c的剩余物中。
找到错误。
在我看来,Valgrind说程序关闭m_file两次。不是个好主意。
为什么程序关闭m_file两次?最有可能的答案是FileLoader的析构函数被调用了两次。
在这里,我进入了一个未定义的领域,因为我所能做的就是从字里行间推断出问题所遗漏的信息。甚至不能关闭这个问题,把它指向什么是三的规则?因为我们不能确定。它可以因为不清楚和无法回答而关闭,所以在它…
以下是我的假设:
- m_file是FileLoader的类成员。如果它不是全局的,这是一个糟糕的设计,不值得尝试修复。
- 在
//...Loading, processing, reading from file and stuff...
的某个地方f
正在被复制。请给f
一个真实的、描述性的名称。您节省的调试时间可能是您自己的。 - FileLoader和ItemLoader违反了三原则。如果你不知道我在说什么,请阅读上面关于三法则的链接。认真对待。阅读它。即使我错了,也要读。
这是如何发生的:
假设你有函数void doesStuff(ItemLoader loader)
,它在//...Loading, processing, reading from file and stuff...
的黑洞中被调用。注意loader的传递值。这意味着它将被复制并成为一个临时变量,其生命周期受函数作用域的限制。
由于FileLoader不符合三规则,loader
获得f
的m_file
和其他成员FileLoader的副本。
doesStuff
做事情并返回。loader
超出作用域并被销毁。~FileLoader()被调用。m_file
不为空,关闭文件,将m_file
设置为空。顺便说一下,没有必要把它设为空。它就要消失了
我们返回到调用函数,f
现在有一个无效的FILE指针,因为复制刚刚关闭了文件。
如何检验理论:
将std::cout << "~FileLoader() called. m_file = " << m_file << std::endl;
放在FileLoader的析构函数的顶部,以查看它被调用的次数与您认为打开它的次数相比。
使你的对象符合三原则,或者使它们不可复制,并且总是通过引用传递。
使FileLoader三规则兼容是一项不平凡的工作。FILE
指针,不能很好地复制,这让你玩弱指针的游戏,以确保吸盘在每个人都完成之前没有关闭。
它也为删除复制构造函数和赋值操作符提供了一个很好的例子,这样FileLoader就不能被复制了。这样,当你试图做一些愚蠢的事情时,编译器可以警告你,比如当你想通过引用传递时,按值传递。
fstream
根本不复制,从一开始就防止您陷入这种混乱。但是如果你不知道一个被删除的函数是什么,它确实会给出一个神秘的错误消息流。
下面是一段测试代码来显示我认为正在发生的事情:
#include <iostream>
#include <cstdio>
class FILETest
{
public:
FILE* filep;
FILETest(): filep(NULL)
{
std::cout << "FILETest constructor" << std::endl;
}
~FILETest()
{
std::cout << "FILETest destructor" << std::endl;
}
};
void func(FILETest t)
{
(void)t;
}
int main(int argc, char** argv)
{
(void) argc;
(void) argv;
FILETest t;
func(t);
return 0;
}
以及fstream
如何防止这种情况发生:
#include <iostream>
#include <fstream>
class fstreamtest
{
public:
std::fstream file;
fstreamtest()
{
std::cout << "fstreamtest constructor" << std::endl;
}
~fstreamtest()
{
std::cout << "fstreamtest destructor" << std::endl;
}
};
void func(fstreamtest t)
{
(void)t;
}
int main(int argc, char** argv)
{
(void) argc;
(void) argv;
fstreamtest t;
func(t);
return 0;
}
标题>
好的,忽略前面的。很抱歉误导了你,因为我忘了告诉你超重要的事实:
- 代码在Win32下100%工作。我在Linux64上编译。
- 文件加载器的析构函数(是虚拟的)并触发。
-
我添加了以下
FileLoader::~FileLoader() { std::cout << "destructor firedn"; if(m_file){ fclose(m_file); m_file = NULL; std::cout << "destructor closed filen"; } delete[] m_buffer; std::cout << "destructor deleted buffern"; for(int i = 0; i < CACHE_BLOCKS; i++){ if(m_cached_data[i].data) delete m_cached_data[i].data; } std::cout << "destructor deleted cachen"; }
输出为:
destructor fired <valgrind crying> destructor closed file
所以它和缓冲区没有任何关系。我发现了问题,版本实际上不匹配,关闭文件,但没有NULL。
这个问题的真正解决方案是Win32 long = Linux64 int。这就解决了部分问题。
- "error: no matching function for call to"构造函数错误
- 表示"accepting anything for this template argument" C++概念的通配符
- 如何在C++中从两个单独的for循环中添加两个数组
- 在Linux for Windows上编译C++代码时出错
- 调用专用模板时出错"no matching function for call to [...]"
- 为什么我的for循环不能正确获取argv
- 运算符C++ "delete []"仅删除 2 个前值
- 为什么我不能在 FOR LOOP 中使用 i/10,C++?
- Arduino:for/while/if在void setup()或void loop()之前?——错误:之前需要不合格
- 在基于范围的for循环中使用结构化绑定声明
- 通过for循环使用用户输入填充列表
- 使用for循环检查数组中的重复项
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 正在使用for循环创建QScatterSerie
- Python中的for循环与C++有何不同
- std::memory_order for std::atomic:<T>:wait
- Valgrind Memcheck工具报告MISS MATCH delete for C ,但删除操作在另一个类/结
- Delete for std::future
- Invalid free() / delete / delete[] / realloc() for fclose()?