C++中malloc/realloc的奇怪行为

3
我正在为自己编写一个动态数组,我希望它预设为零。
template <class T>
dynArr<T>::dynArr()
{
rawData = malloc(sizeof(T) * 20); //we allocate space for 20 elems
memset(this->rawData, 0, sizeof(T) * 20); //we zero it!
currentSize = 20;
dataPtr = static_cast<T*>(rawData); //we cast pointer to required datatype.
}

这部分是可以的 - 通过循环迭代并解除引用dataPtr的方式非常好。全是零。

然而,重新分配行为(在我看来)至少有点奇怪。首先你需要看一下重新分配的代码:

template <class T>
void dynArr<T>::insert(const int index, const T& data)
{

    if (index < currentSize - 1)
    {
        dataPtr[index] = data; //we can just insert things, array is zero-d
    }

    else
    {
        //TODO we should increase size exponentially, not just to the element we want

        const size_t lastSize = currentSize; //store current size (before realloc). this is count not bytes.

        rawData = realloc(rawData, index + 1); //rawData points now to new location in the memory
        dataPtr = (T*)rawData;
        memset(dataPtr + lastSize - 1, 0, sizeof(T) * index - lastSize - 1); //we zero from ptr+last size to index

        dataPtr[index] = data;
        currentSize = index + 1;
    }

}

简单来说,我们会将数据重新分配到索引+1的位置,并将未清零的内存设为0。
关于测试,我首先在这个数组的第5个位置插入了数字5。预期的结果发生了——0,0,0,0,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 然而,插入其他东西,比如insert(30,30),会给我带来奇怪的行为:
0, 0, 0, 0, 0, 5, 0, -50331648, 16645629, 0, 523809160, 57600, 50928864, 50922840, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30,

我到底是哪里理解错了?realloc不应该考虑之前设置的20个内存字节吗?这里到底发生了什么黑魔法。


1
你在realloc调用中忘记将(index+1)乘以sizeof(T) - FBergo
1
rawData = realloc(rawData, index + 1); 如果这个操作失败了,你将会失去所有的数据。 - Baum mit Augen
3
同时,mallocrealloc返回的原始内存块不包含任何对象,在使用放置new分配内存之前,必须先创建对象并进行赋值操作。否则将导致未定义的行为。 - Baum mit Augen
dataPtr[index] = data; 是错误的语法,这不是C语言... - Swift - Friday Pie
1个回答

4

问题 1:

在调用 realloc 时您使用了错误的大小。请将其更改为:

rawData = realloc(rawData, sizeof(T)*(index + 1)); 

如果rawData的类型是T*,则应优先考虑
rawData = realloc(rawData, sizeof(*rawData)*(index + 1)); 

问题2:

以下内容的最后一项不正确。

memset(dataPtr + lastSize - 1, 0, sizeof(T) * index - lastSize - 1); 

您需要使用:

memset(dataPtr + lastSize - 1, 0, sizeof(T) * (index - lastSize - 1));
                               //  ^^              ^^
                               // size      *  The number of objects 

问题三:

使用如下方式为dataPtr赋值

dataPtr[index] = data;

使用mallocrealloc获取内存时会存在问题。 malloc函数族只返回原始内存,它们不会初始化对象。对于所有非POD类型,分配给未初始化的对象是一个问题。

问题4:

如果T是具有虚成员函数的类型,则使用memset将内存清零很可能会导致问题。


解决所有问题的建议:

在C++中最好使用newdelete

template <class T>
dynArr<T>::dynArr()
{
   currentSize = 20;
   dataPtr = new T[currentSize];
   // Not sure why you need rawData
}

template <class T>
void dynArr<T>::insert(const int index, const T& data)
{
   if (index < currentSize - 1)
   {
      dataPtr[index] = data;
   }

   else
   {
      const size_t lastSize = currentSize;
      T* newData = new T[index+1];
      std::copy(dataPtr, dataPtr+lastSize, newData);
      delete [] dataPtr;
      dataPtr = newData;
      dataPtr[index] = data;
      currentSize = index + 1;
   }
}

请注意,建议的更改仅在T具有默认构造函数时才有效。
这也会解决上述问题3和4。

1
@Swift 这是一个有意思的问题。在什么情况下,零值对象进行(或许是移动)赋值将会无效? - Captain Giraffe
1
@CaptainGiraffe 如果它不是标准布局\POD类,也可能会导致编译器出现问题。从技术上讲,除非使用了放置new,否则那里发生的是未定义行为。后者将在分配的内存中构造对象...这就是内存池的工作原理。 - Swift - Friday Pie
2
@Swift 只要对象没有非空初始化,那么这是完全合法的。如果有非空初始化,则需要使用放置 new。 - NathanOliver
1
在编程中,将指针转换类型并不会创建它所指向的内存中的对象。 - Baum mit Augen
1
@shajduk 这有点技术性,即使是非常优秀的C++专家有时也会感到困惑。但简单来说:你不是将值分配给内存位置,而是将值分配给对象。在这之前,内存必须包含一个对象,而malloc不会创建任何对象。因此,您需要使用放置new。 (说实话,正确处理像std::vector这样的容器及其所有边角情况真的很难,我不确定我自己能否做到。这花了微软很多年时间。) - Baum mit Augen
显示剩余17条评论

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