c++ - 使用 C++ 异常的内存泄漏

标签 c++ c++11 memory-leaks exception filehandler

我正在努力用 C++11 编写一个小文件句柄类。 我知道 STL 中已经有很多内容可以实际处理文件,但出于学习目的,我想自己做。

不幸的是,我似乎不明白异常对 C++ 程序的内存泄漏行为有什么影响,因为 Valgrind 告诉我,以下代码中有 2 处内存泄漏:

文件.h

#ifndef FILE_H
#define FILE_H

#include <iostream>
#include <memory>
#include <string>

#include <stdio.h>

class FileDeleter {
public:
    void operator()(FILE *p);
};

class File {
public:
    File(const std::string path);

private:
    const std::string _path;
    std::unique_ptr<FILE, FileDeleter> _fp;

};

#endif // FILE_H

文件.cpp

#include <cstring>
#include <iostream>
#include <stdexcept>
#include <utility>

#include <stdio.h>

#include "file.h"

void FileDeleter::operator()(FILE *p)
{
if (!p)
    return;

    if (fclose(p) == EOF)
        std::cerr << "FileDeleter: Couldn't close file" << std::endl;
}

File::File(const std::string path)
    : _path(std::move(path)),
      _fp(fopen(_path.c_str(), "a+"))
{
    if (!_fp)
        throw std::runtime_error("Couldn't open file");
}

主要.cpp

#include <cstdlib>
#include "file.h"

int main()
{
    File my_file("/root/.bashrc");

    return EXIT_SUCCESS;
}

我确实选择打开/root/.bashrc 是为了让 File ctor 抛出异常。如果我不扔在那里,Valgrind 会非常高兴。 我在这里使用异常时缺少什么? 如何“正确”(异常安全)实现简单的文件句柄?

提前致谢!

注意:读/写操作仍然缺失,因为我已经在努力学习基础知识了。

编辑#1: 这是实际的 Valgrind 输出,使用 --leak-check=full:

==7998== HEAP SUMMARY:
==7998==     in use at exit: 233 bytes in 3 blocks
==7998==   total heap usage: 5 allocs, 2 frees, 817 bytes allocated
==7998== 
==7998== 38 bytes in 1 blocks are possibly lost in loss record 1 of 3
==7998==    at 0x4C27CC2: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7998==    by 0x4EEC4F8: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEDC30: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEE047: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x40137D: main (in ../a.out)
==7998== 
==7998== 43 bytes in 1 blocks are possibly lost in loss record 2 of 3
==7998==    at 0x4C27CC2: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7998==    by 0x4EEC4F8: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEDC30: char* std::string::_S_construct<char const*>(char const*, char const*, std::allocator<char> const&, std::forward_iterator_tag) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x4EEE047: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x400EBF: File::File(std::string) (in /home/frank/3Other/Code/Laboratory/c++/c++namedpipe/a.out)
==7998==    by 0x401390: main (in ../a.out)
==7998== 
==7998== 152 bytes in 1 blocks are possibly lost in loss record 3 of 3
==7998==    at 0x4C27730: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7998==    by 0x4E8F8F2: __cxa_allocate_exception (in /usr/lib/libstdc++.so.6.0.18)
==7998==    by 0x400E9B: File::File(std::string) (in /home/frank/3Other/Code/Laboratory/c++/c++namedpipe/a.out)
==7998==    by 0x401390: main (in ../a.out)
==7998== 
==7998== LEAK SUMMARY:
==7998==    definitely lost: 0 bytes in 0 blocks
==7998==    indirectly lost: 0 bytes in 0 blocks
==7998==      possibly lost: 233 bytes in 3 blocks
==7998==    still reachable: 0 bytes in 0 blocks
==7998==         suppressed: 0 bytes in 0 blocks

编辑#2: 修复了 Destructor 中抛出的异常。

编辑#3: 移除了 FileException 类,改为使用 std::runtime_error。

编辑#4: 在删除器中添加了 NULL 检查。

最佳答案

我看到以下问题:

  1. fclose(NULL) 不允许...
    • 但幸运的是,如果 get() == nullptrstd::unique_ptr 不会调用删除器,所以这里应该没问题
  2. FileDeleter:operator() 从析构函数中调用,这意味着它永远不会抛出。在您的特定情况下,从 fclose 返回的任何错误都将导致调用 std::terminate
  3. 您的异常应按值而非引用存储字符串。该引用是一个临时文件,在您调用 what() 时将被销毁。
    • 正如 Vlad 和 Praetorian 指出的那样,您可以删除错误代码而不是修复它,只需让 std::runtime_error 为您处理。 TBH,除非您计划向异常添加一些特定于文件的数据,或者您计划单独捕获文件异常,否则您根本不需要此类。

编辑:在提供 valgrind 输出的情况下,堆栈展开似乎从未完成。由于我最初的想法是调用 FileDeleter::operator() 并抛出错误,因此合理的测试是:如果将 try/catch 插入 main 的主体内会发生什么情况?


一般说明:

  1. 永远不要在析构函数中抛出异常
  2. 永远不要从析构函数中调用可能抛出的其他东西

因为如果在异常处理/堆栈展开期间调用您的析构函数,程序将立即终止。

关于c++ - 使用 C++ 异常的内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20575291/

相关文章:

c++ - 分配数组时是否可以将参数传递给 std::make_unique() ?

java - 如何释放多个 org.hibernate.impl.SessionFactoryImpl

c++ - 绳索数据结构的拆分操作

c++ - 如何检查一个类是否在 C++ 中声明?

c++ - 指向 vector 迭代器返回值的指针

java - Files.getLastModifiedTime() 是否泄漏内存?

c++ - 与视觉检漏仪泄漏对应的代码

c++ - 进程 ID 和进程名称

C++11:当定义移动构造函数时,按值返回对象不会抛出异常吗?

c++ - 是否有任何选项可以推迟可变参数模板中函数调用的评估