“new/malloc”应该“delete/free”的人

The one who `new/malloc` should `delete/free`?

本文关键字:free 的人 delete 应该 new malloc      更新时间:2023-10-16

在C/C++中,new/malloc某个内存的函数应该在函数完成之前delete/free它们,这似乎是一个常识,对吧?

但假设我有这样的问题,有一个函数reader会将文件中的块读取到缓冲区中,还有另一个函数consumer会消耗这些缓冲区,

void reader(ifstream &ifs)
{
    char *buf = new char[100];
    ifs.read(buf, 100);
    save_buffer(buf);  //save this buf into a global array, say buf_array[10]
}
void consumer()
{
    for(int i = 0; i < 10; i++)
        consume( buf_array[i] );  //do something with those buffers read by reader
}

我的问题是,许多内存资源都在readernew,但reader不能delete,因为这些缓冲区还没有被consumer使用。consumer是否应该负责delete处理这些缓冲区?

没有人说分配内存的函数应该释放内存。但通常应该由同一个组件来处理。因为readerconsumer是一对,所以它们可以一起协调内存。

在C/C++中,new/malloc一些内存的函数应该在函数完成之前delete/free它们,这似乎是一个常识,对吧?

不,这不一定是真的,你不必在同一个函数中释放内存,只要你最终在程序结束前释放它。

C++中(但C中没有)的一个常见解决方案是解除析构函数中内存的分配。如果在正确处理复制/移动构造函数和赋值运算符的同时传递包含动态分配内存的对象,则在调用析构函数时将释放内存。

原则是"对于每个new,都应该有一个delete"。这并没有说明两个调用必须在同一个函数内(显然,这不是很有用)。

读者分配和消费者释放的例子没有问题。

您不需要在初始化缓冲区的同一函数中释放已分配的缓冲区,只要您携带一个指向缓冲区的指针即可。

在您的情况下,consumer()应该负责delete分配给consumer的缓冲区。

至于你的第二个问题,消费者不知道缓冲区在哪里结束;你需要以某种方式告诉它。您可以考虑定义一个新的struct来封装缓冲区及其长度,而不是只存储指向缓冲区的指针。这已经完成了:考虑使用std::string

您正在将buf的内容复制到一个全局数组buf_array,该数组实际上为消费者提供信息。因此,在上面的例子中,读者可以释放buf。

而且,也没有要求新的mallocs应该释放内存的函数。指针可以四处传递。最后使用分配内存的函数需要释放内存。

您不必在同一函数中执行new/malloc(分配)和delete/free(发布)。只要你的算法保证每个分配都会被释放,并且只被释放一次,这样就不会导致内存泄漏,这是可以的
事实上,分配和发布通常存在于单独的功能中
请记住:
1.使用相同的指针进行释放(当然,您可以将指针四处传递)。如果对指针进行一些运算,然后使用修改后的指针释放分配,即使指针仍然指向分配区域,也会产生错误
2.如上所述,您应该保证分配被释放,并且只释放一次。附加释放会导致错误。

与其他人的答案略有不同:

是的,阅读器在读取功能中不释放/删除内存是可以的,但我不会让消费者删除内存。在一个简单的情况下是可行的,但如果你有多个消费者(例如以不同的格式输出)怎么办?如果消费数据有释放数据的副作用,那么在第一个消费者做了它的事情之后,你就不能对数据做任何其他事情。

我的阅读器中会有一个cleanup()类型的方法,我会在需要时显式调用它来清理缓冲区。通过这种方式,分配内存的模块负责释放内存(即使它使用不同的方法)。

例如

Data d = Reader.read();
Consumer1.consume(d);
Consumer2.consume(d);
Reader.cleanup(d);
// d is no longer valid.

您还可以阅读有关共享数组的内容http://www.boost.org/doc/libs/1_35_0/libs/smart_ptr/shared_array.htm

使用共享数组,可以在函数内部分配它们,并按值将它们传递给其他函数。共享数组保留其被引用次数的内部计数,并在所有引用超出范围时自动删除内存。

例如

void bar( boost::shared_array< int > );
void foo()
{
    boost::shared_array< int > x ( new int[ 100 ] );
    bar( x );
    // because x has been passed to bar(), its reference count is incremented
    // and it will not only be deleted when everyone is finished with it!
    // do more things with x
} // the memory held by x is deleted
void bar( boost::shared_array< int > y )
{
    // Do things with y here
} // the memory held by y is not deleted here because foo() hasn't yet finished

按偏好降序排列:

  1. 使用auto存储类,因此会自动删除
  2. 使用相同的代码分配和删除对象
  3. 传递所有权,这样一个组件分配,另一个组件释放
  4. 通过类似shared_ptr的方式共享所有权

3和4几乎打平,但都远远落后于2,后者远远落后于1。

在这种情况下,真正喜欢的是在生产者和消费者之上的函数级别(或类等)来分配缓冲区,启动生产者和消费者,并在两者都完成时删除缓冲区:

class buffer { 
     std::vector<char> data;
public:
     buffer() : data(100) {}
};
process_file(std::string const &name) { 
    std::vector<buffer> buffers(10);
    std::ifstream in(name);
    std::pair<std::ifstream *, std::vector<buffer> *> thread_data = {
        &in, &buffers
    };
    prod = start_thread(producer, (void *)&thread_data);
    cons = start_thread(consumer);
    join(prod);
    join(cons);
}

如果你能做到这一点,它可以帮助避免在管理内存时出现很多头痛的问题。