使用新运算符和2D数组来防止内存分配失败的正确方法

时间:2012-03-27 19:13:54

标签: c++ memory-management

在下面的代码中是创建2D数组时所需的初始第一个循环,或者如果存储器分配失败,指向实际行的指针是否会初始化为NULL?

unsigned char **row_pointers;
try
{
  row_pointers = new unsigned char *[height];
  for (int i = 0; i < height; ++i)
    row_pointers[i] = NULL;
  for (int i = 0; i < height; ++i)
    row_pointers[i] = new unsigned char[width];
}
catch (std::bad_alloc)
{
  throw std::runtime_error("Failure to allocate memory for raw data");
}

更新

为了澄清,我正在看的代码是:

149   /*
150    * Allocate sufficient space for the data
151    */
152   unsigned char **row_pointers;
153   try
154   {
155     row_pointers = new unsigned char *[height]();
156     for (int i = 0; i < height; ++i)
157       row_pointers[i] = new unsigned char[width];
158   }   
159   catch (std::bad_alloc)
160   {
161     /*
162      * If insufficient memory than try and clean up
163      * and throw runtime error
164      */ 
165     for (int i = 0; i < height; ++i)
166     { 
167       if (row_pointers[i] != NULL)
168       { 
169         delete row_pointers[i];
170       }
171     }
172     throw std::runtime_error("Failure to allocate raw memory for data");
173   }
...   // White Space
177 
178   /*
179    * Now read the data all at once (no need to handle interlacing
180    */
181   png_read_image(m_pPNG, row_pointers);
182 
183   for (int i = 0; i < height; ++i)
184   {
185     for (int j = 0; j < width; ++j)
186       std::cout << row_pointers[i][j];
187     std::cout << std::endl;
188   }

4 个答案:

答案 0 :(得分:3)

此操作未初始化分配有new unsigned char*[height]的数组。如果任何分配失败,它将抛出异常。在您的代码中,您接下来初始化您的数组。我认为这应该是这样的:

std::fill_n(row_pointers, height, 0);

当然,如果任何后续分配失败,这个数组将会泄露,到目前为止所分配的所有其他数组都将泄露。你可以在catch - 块中清理这个混乱。

就个人而言,我无法应对多次清理:只是太复杂而无法做到正确。就个人而言,我会使用两个std::vector<T>代替,捆绑到一个类中:

  • 一个std::vector<unsigned char*>被初始化为指向子向量的开头
  • 一个std::vector<unsigned char>来容纳所有子向量

一旦分配了这些指针,第一个向量中的指针就会被设置为指向适当位置的第二个向量。方便的是,如果出现任何问题,两个向量的析构函数将负责清理。

这是这样的:

#include <vector>
#include <cstddef>

struct array2d
{
    array2d(std::size_t height, std::size_t width)
        : inner_(height * width)
        , outer_(height)
    {
        for (std::size_t i(0); i != height; ++i) {
            this->outer_[i] = &this->inner_[i * width];
        }
    }

    unsigned char** get() { return &this->outer_[0]; }

    std::vector<unsigned char> inner_;
    std::vector<unsigned char*> outer_;
};

当你得到这种类型的对象picture时,你可以使用picture.get()来获得适合传递给C函数的指针。

答案 1 :(得分:1)

这取决于编译器和选项,但为了安全起见,您应该保留初始化,如果您期望它。

某些带有debug选项的编译器将使用0值初始化row_pointers数组。其他人会用标记填充它(如0xcc),而其他人则会将其保留为未初始化状态。

我不确定,但我认为可能还有一些选项会返回NULL而不是抛出std::bad_alloc,在这种情况下,额外的初始化将是不必要的。

然而,如果有疑问,请明确!

答案 2 :(得分:0)

这对你来说可能是一个很好的解读:

http://www.cplusplus.com/reference/std/new/operator%20new/

答案 3 :(得分:-2)

您要编译哪个操作系统?对于某些(例如linux),new将永远不会失败,相反,当您使用内存时,您将在稍后获得段违规!

在这种情况下,您将在第一次初始化循环期间触发段违规。

这可能是阅读一些Herb Sutters GOTW

的好时机

请参阅:GOTW - To New, Perchance To Throw, Part 2

注意:在新增任一维度后,您可以使用memset初始化数据,因此第一个循环可以由memset( row_pointers, sizeof(unsigned char *)*height, 0 )替换。