fclose()的free()/ delete / delete [] / realloc()无效?

时间:2015-09-12 17:19:19

标签: c++ linux free delete-operator

我尝试在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*/};

现在到项目中的功能:

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(如果这有任何事情要做)。 我试图尽可能具体

4 个答案:

答案 0 :(得分:2)

Vagrind直接向您显示问题 - 您在同一个fclose描述符上两次调用FILE

            ==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中的第一个调用。

答案 1 :(得分:1)

看起来FileLoader存在一种形式的缓存/缓冲。使用FILE*或IOStream时,无需自行缓冲,他们会为您执行此操作。 FileLoader在其上添加了另一个缓冲区。

您可能希望重构FileLoader以放弃所有缓冲并仅为您的类提供序列化功能,同时将所有I / O和相关缓冲委派给FILE*或IOStreams。

答案 2 :(得分:1)

房间里的大象:Ahrrr。这是C ++。为什么要使用FILE? fstream将为你处理这些废话(好吧......其中一部分)而不会掉进C的剩余部分。

在错误上。

在我看来,Valgrind说程序正在关闭m_file两次。不是个好主意。

为什么程序关闭m_file两次?最有可能的答案是FileLoader析构函数被调用两次。

在这里,我徘徊在未定义的领域,因为我能做的就是从行之间的阅读中推断出问题中遗漏的信息。甚至无法关闭此问题并将其指向What is The Rule of Three?,因为我们无法确定。它可以因为不清楚而无法接受而关闭,所以在它之前......

以下是我的假设:

  1. m_file是FileLoader的类成员。如果它不是并且它是全球性的,这是一个不值得尝试解决的糟糕设计。
  2. 正在复制//...Loading, processing, reading from file and stuff... f中某处的
  3. 。请给f一个真实的描述性名称。您保存的调试时间可能是您自己的。
  4. FileLoader和ItemLoader违反了规则三。如果你不知道我在说什么,请阅读上面关于三规则的链接。认真。阅读。即使我错了,也请阅读。
  5. 如何发生这种情况:

    假设你有void doesStuff(ItemLoader loader)函数,它会在//...Loading, processing, reading from file and stuff...的黑洞中被调用。注意loader的pass by值。这意味着它将被复制并成为一个临时变量,其寿命受到函数范围的限制。

    由于FileLoader不符合三级规则,loader会获得f的{​​{1}}副本以及FileLoader拥有的其他成员。

    m_file完成并返回。 doesStuff超出范围并被销毁。 〜调用FileLoader()。 loader不为空,文件已关闭,m_file设置为null。顺便说一下,没有必要将它设置为null。它即将消失。

    我们返回调用函数,其中m_file现在有一个无效的FILE指针,因为该副本只是关闭了文件。

    如何测试理论:

    f放在FileLoader析构函数的顶部,现在可以看到它被调用的次数与您认为打开它的次数相比。

    修复:

    使你的对象符合三级规则或使它们不可复制并始终通过引用传递。

    使FileLoader Rule of Three合规是非常重要的。 std::cout << "~FileLoader() called. m_file = " << m_file << std::endl;指针并且不能很好地复制,这会让你玩弱指针的游戏,以确保在每个人都完成它之前没有关闭吸盘。

    它也为deleting the copy constructor and assignment operator提供了一个好例子,因此无法复制FileLoader。这样,当你想要通过引用传递时,编译器可以在你尝试做一些像传递值这样的哑巴时警告你。

    FILE根本不复制,防止你首先陷入困境。但如果您不知道删除的函数是什么,它确实会提供一个神秘的错误消息流。

    这是一段测试代码,用以显示我认为发生的事情:

    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

答案 3 :(得分:0)

好的,不要理会以前的。很抱歉误导,因为我忘了告诉 metaimportant 事实:

  1. 代码100%适用于Win32。我正在Linux64上编译它。
  2. FileLoader的析构函数(是虚拟的)并触发。
  3. 我添加了以下内容

    FileLoader::~FileLoader() {
       std::cout << "destructor fired\n";
       if(m_file){
           fclose(m_file);
           m_file = NULL;
           std::cout << "destructor closed file\n";
        }
    
        delete[] m_buffer;
        std::cout << "destructor deleted buffer\n";
    
       for(int i = 0; i < CACHE_BLOCKS; i++){
            if(m_cached_data[i].data)
               delete m_cached_data[i].data;
            }
       std::cout << "destructor deleted cache\n";
    }
    

    输出结果为:

    destructor fired
    <valgrind crying>
    destructor closed file
    

    所以它与缓冲区没有任何关系。我发现问题,版本实际上不匹配,关闭文件,但没有NULL。因此实际上是原始代码中的错误。

  4. 问题的真正解决方案是Win32 long = Linux64 int。这解决了部分问题。