如何在C++中使用'realloc'函数?

102

我如何在C++中使用realloc函数?似乎该语言缺少这个函数 - 它有newdelete但没有resize

我需要它因为我的程序读取更多数据时,需要重新分配缓冲区以存储它。我认为删除旧指针并用新的、更大的指针来进行new操作不是正确的选择。


11
Stroustrup很早以前就回答过这个问题了,参见:http://www2.research.att.com/~bs/bs_faq2.html#renew(如果你是C++的新手,可以和Cline的C++ FAQ一起阅读,这是一个不错的开始。) - dirkgently
12
@dirkgently提到的答案现在在此链接中:http://www.stroustrup.com/bs_faq2.html#renew - Cline的FAQ现在是超级FAQ的一部分:https://isocpp.org/faq。 - maxschlepzig
@dirkgently 这个链接已经失效了,您可以更新一下参考资料吗? - CodeTalker
4个回答

63
使用 ::std::vector!
Type* t = (Type*)malloc(sizeof(Type)*n) 
memset(t, 0, sizeof(Type)*m)

成为

::std::vector<Type> t(n, 0);

那么

t = (Type*)realloc(t, sizeof(Type) * n2);

变成

t.resize(n2);

如果您想将指针传递到函数中,而不是使用 & 运算符,可以按照以下方式进行操作:

Foo(t)

使用

Foo(&t[0])

这是绝对正确的C++代码,因为vector是智能的C数组。


2
memset那一行不应该是memset(t, 0, sizeof(T) * n);吗?应该用n而不是m吧? - Raphael Mayer
1
@anthom 是的。应该这样写:Type* t = static_cast<Type*>(malloc(n * sizeof *t)); - Ryan Haining
2
使用C++11,现在应该使用t.data()而不是&t[0] - knedlsepp
1
那么你怎么删除它呢? - a3mlord
@a3mlord:你的意思是什么?让它超出作用域,它就消失了。 - Lightness Races in Orbit
显示剩余3条评论

54

正确的选择可能是使用可以为您完成工作的容器,例如 std::vector

newdelete 无法调整大小,因为它们只分配足以容纳给定类型对象的内存。给定类型的大小永远不会改变。虽然有 new[]delete[],但几乎没有理由使用它们。

C 中 realloc 所做的可能只是一个 mallocmemcpyfree,尽管内存管理器允许在有足够连续的空闲内存可用时执行一些聪明的操作。


2
那么在C++中,实现一个动态增长的缓冲区应该怎么做呢?目前我是这样做的:char *buf = (char *)malloc(size),当它变得太小时,我会执行buf = realloc(size + more_size); size += more_size。那么如何使用vector来实现呢? - bodacydo
6
不要实现增长缓冲区,只需使用std::vector-需要时它会自动增长,如果需要可以预先分配内存(使用reserve())。 - sharptooth
4
使用std::vector<T>,这就是它的用途。在C++中,除非您明确编写资源管理类,否则没有任何理由自己使用new/delete/new[]/delete[]。 - Puppy
4
可以的,std::string 也可以。 - fredoverflow
2
听起来像是 thevector.resize(previous_size + incoming_size),然后跟着一个 memcpy(或类似的函数)到 &thevector[previous_size] 就是你需要的。这个向量的数据保证被存储“像一个数组”。 - Thomas
显示剩余6条评论

44

由于可能需要调用构造函数和析构函数,因此在C++中进行大小调整非常麻烦。

我认为,在C ++中不能有与new []delete []配套使用的resize[]操作符并不是基本原因,这个操作符可以做类似以下的事情:

newbuf = new Type[newsize];
std::copy_n(oldbuf, std::min(oldsize, newsize), newbuf);
delete[] oldbuf;
return newbuf;
显然,oldsize将从一个秘密的位置中检索出来,就像在delete[]中一样,并且Type将根据操作数的类型进行选择。当类型不可复制时,resize[]会失败,这是正确的,因为这样的对象根本无法被重定位。最后,上述代码在赋值之前默认构造对象,而实际上您不希望这样做。

如果newsize <= oldsize,则有可能的优化是调用新缩小数组“结束后”的对象的析构函数,然后什么也不做。标准必须定义该优化是否是必需的(例如,当你resize()一个向量时),允许但未指定,允许但依赖于具体实现,还是禁止。

您应该问自己的问题是,“提供这个功能是否真的有用,考虑到vector已经具备了这个功能,并且专门设计为提供一个更适合C++方式的可调整大小的容器(连续内存-这个要求在C++98中省略了但在C++03中修正)?”

我认为普遍认为这个答案是“不需要”。如果您想以C方式使用可调整大小的缓冲区,请使用在C++中可用的malloc / free / realloc。如果您想以C++方式使用可调整大小的缓冲区,请使用向量(或deque,如果您实际上不需要连续存储)。除非您正在实现类似于向量的容器,否则不要尝试通过使用new[]来混合两者。


2006年,有一些与调整大小相关的有趣提案,但当时并没有得到关注。我不知道是否有更新版本。链接如下:https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n1953.html https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1085.htm https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2045.html。 - Bruce Adams

0
这是一个使用realloc实现简单向量(每次达到限制时*2)的std::move示例。如果有比我下面的复制更好的方法,请告诉我。
编译为:
  g++ -std=c++2a -O2 -Wall -pedantic foo.cpp

代码:

#include <iostream>
#include <algorithm>

template<class T> class MyVector {
private:
    T *data;
    size_t maxlen;
    size_t currlen;
public:
    MyVector<T> () : data (nullptr), maxlen(0), currlen(0) { }
    MyVector<T> (int maxlen) : data (new T [maxlen]), maxlen(maxlen), currlen(0) { }

    MyVector<T> (const MyVector& o) {
        std::cout << "copy ctor called" << std::endl;
        data = new T [o.maxlen];
        maxlen = o.maxlen;
        currlen = o.currlen;
        std::copy(o.data, o.data + o.maxlen, data);
    }

    MyVector<T> (const MyVector<T>&& o) {
        std::cout << "move ctor called" << std::endl;
        data = o.data;
        maxlen = o.maxlen;
        currlen = o.currlen;
    }

    void push_back (const T& i) {
        if (currlen >= maxlen) {
            maxlen *= 2;
            auto newdata = new T [maxlen];
            std::copy(data, data + currlen, newdata);
            if (data) {
                delete[] data;
            }
            data = newdata;
        }
        data[currlen++] = i;
    }

    friend std::ostream& operator<<(std::ostream &os, const MyVector<T>& o) {
        auto s = o.data;
        auto e = o.data + o.currlen;;
        while (s < e) {
            os << "[" << *s << "]";
            s++;
        }
        return os;
    }
};

int main() {
    auto c = new MyVector<int>(1);
    c->push_back(10);
    c->push_back(11);
}

你的实现示例存在一些问题。其中最相关的有以下几点:移动构造函数只是将源对象的成员赋值给目标对象,使得两者都指向同一个缓冲区,因此对资源的更新操作会影响到另一个对象;移动构造函数不接受const参数;push_back()成员函数不应直接将值分配给数组位置,因为它是一个未初始化的内存缓冲区,如果复制构造函数不是平凡的话,可能会导致未定义行为。 - LoS
最重要的是,在没有用户定义的析构函数的情况下,资源无法正确释放(例如,如果在对象销毁时没有调用delete[],而之前使用new[]分配了内存,则会发生内存泄漏)。 - LoS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接