Правильный способ защиты от неудачных выделений памяти с помощью оператора new и 2D Array - PullRequest
1 голос
/ 27 марта 2012

В приведенном ниже коде необходим ли первый начальный цикл при создании двумерного массива, или при неудачном выделении памяти указатели на фактические строки будут инициализированы значением 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");
}

UPDATE

Чтобы уточнить, код, на который я смотрю:

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 ]

3 голосов
/ 27 марта 2012

Массив, выделенный с new unsigned char*[height], не инициализируется этой операцией. Если какое-либо распределение завершится неудачно, оно выдаст исключение. В вашем коде вы затем инициализируете свой массив. Я думаю, это должно выглядеть так:

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

Конечно, если какое-либо последующее распределение завершится неудачно, этот массив будет просочиться, как и все остальные массивы, выделенные до сих пор. Вы можете убрать этот беспорядок в catch -блоке.

Лично я не могу справиться с несколькими очистками: это слишком сложно сделать правильно. Лично я бы вместо этого использовал два std::vector<T>, связанных в класс:

  • one 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 голос
/ 27 марта 2012

Это зависит от компилятора и опций, но для безопасности следует сохранить инициализацию, если вы этого ожидаете.

Некоторые компиляторы с опцией отладки инициализируют массив row_pointers с 0 значениями. Другие заполнят его маркерами (например, 0xcc), а другие просто оставят его неинициализированным.

Я не уверен, но я думаю, что могли бы также быть опции, которые возвращали бы NULL вместо броска std::bad_alloc, в этом случае дополнительная инициализация была бы ненужной.

Однако, если вы сомневаетесь, будьте откровенны!

0 голосов
/ 27 марта 2012

Для какой ОС вы компилируете?С некоторыми (например, linux) new никогда не завершится неудачей, вместо этого вы получите нарушение сегмента позже, когда будете использовать память!

В этом сценарии вы вызовете нарушение сегмента во время первого цикла инициализации.

Это может быть хорошее время, чтобы прочитать некоторые из травяных Саттеров GOTW

см .: GOTW - до нового, вероятность броска, часть 2

Примечание: после обновленияВ любом измерении вы можете использовать memset для инициализации данных, поэтому первый цикл можно заменить на memset( row_pointers, sizeof(unsigned char *)*height, 0 ).

0 голосов
/ 27 марта 2012

Это может быть хорошим чтением для вас:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...