Проблемы с malloc на OS X - PullRequest
       37

Проблемы с malloc на OS X

2 голосов
/ 16 октября 2010

Я написал некоторый код C, работающий на OS X 10.6, который работает медленно, поэтому я использую valgrind для проверки на утечки памяти и т. Д. Одна из вещей, которые я заметил, делая это:

Если я выделю память для двумерного массива следующим образом:

double** matrix = NULL;
allocate2D(matrix, 2, 2);

void allocate2D(double** matrix, int nrows, int ncols) {
    matrix = (double**)malloc(nrows*sizeof(double*));
    int i;
    for(i=0;i<nrows;i++) {
        matrix[i] = (double*)malloc(ncols*sizeof(double));
    }
}

Затем проверьте адрес памяти матрицы, это 0x0.

Однако, если я сделаю

double** matrix = allocate2D(2,2);

double** allocate2D(int nrows, int ncols) {
    double** matrix = (double**)malloc(nrows*sizeof(double*));
    int i;
    for(i=0;i<nrows;i++) {
        matrix[i] = (double*)malloc(ncols*sizeof(double));
    }
return matrix;
}

Это прекрасно работает, то есть возвращается указатель на вновь созданную память.

Когда у меня также есть функция free2D для освобождения памяти. Кажется, это не освобождает должным образом. То есть указатель по-прежнему указывает на тот же адрес, что и до вызова free, а не 0x0 (который, я думал, может быть по умолчанию).

void free2D(double** matrix, int nrows) {
    int i;
    for(i=0;i<nrows;i++) {
        free(matrix[i]);
    }
free(matrix);
}

Мой вопрос: не понимаю ли я, как работает malloc / free? Иначе кто-нибудь может подсказать, что происходит?

Alex

Ответы [ 3 ]

4 голосов
/ 16 октября 2010

Когда вы освобождаете указатель, значение указателя не меняется, вам придется явно установить его в 0, если вы хотите, чтобы он был нулевым.

3 голосов
/ 16 октября 2010

В первом примере вы храните только указатель, возвращаемый malloc, в локальной переменной. Он теряется, когда функция возвращается.

Обычная практика на языке Си - использовать возвращаемое значение функции для передачи указателя на выделенный объект обратно вызывающей стороне. Как указал Армен, вы также можете передать указатель на то, где функция должна хранить свои выходные данные:

void Allocate2D(double*** pMatrix...)
{
   *pMatrix = malloc(...)
}

но я думаю, что большинство людей будут кричать, как только увидят ***.

Вы также можете считать, что массивы указателей не являются эффективной реализацией матриц. Выделение каждой строки по отдельности способствует фрагментации памяти, malloc накладным расходам (поскольку каждое выделение требует некоторого учета, не говоря уже о дополнительных указателях, которые вы должны хранить), и отсутствию кэширования. И каждый доступ к элементу матрицы включает в себя 2 разыменования указателя, а не только одну, что может привести к задержкам. Наконец, у вас есть намного больше работы, чтобы выделить матрицу, так как вам нужно проверять ошибки каждого malloc и очищать все, что вы уже сделали, если какой-либо из них потерпит неудачу.

Лучше всего использовать одномерный массив:

double *matrix;
matrix = malloc(nrows*ncols*sizeof *matrix);

затем получите доступ к элементу (i, j) как matrix[i*ncols+j]. Потенциальными недостатками являются умножение (которое медленно у древних процессоров, но быстро у современных) и синтаксис.

Еще лучший подход - не стремиться к избыточной общности. Большая часть матричного кода в SO предназначена не для продвинутой числовой математики, где могут потребоваться произвольные размеры матриц, а для трехмерных игр, где 2x2, 3x3 и 4x4 являются единственными размерами матриц любого практического использования. Если это так, попробуйте что-то вроде

double (*matrix)[4] = malloc(4*sizeof *matrix);

и затем вы можете получить доступ к элементу (i, j) как matrix[i][j] с единственной разыменовкой и чрезвычайно быстрым умножением на константу. И если ваш matrix нужен только в локальной области видимости или внутри структуры, просто объявите его как:

double matrix[4][4];

Если вы не очень хорошо разбираетесь в системе типов C и приведенных выше декларациях, лучше всего в любом случае просто обернуть все ваши матрицы в структуры:

struct matrix4x4 {
    double x[4][4];
};

Тогда объявления, приведение указателей, распределение и т. Д. Становятся намного более знакомыми. Единственным недостатком является то, что вам нужно сделать что-то вроде matrix.x[i][j] или matrix->x[i][j] (в зависимости от того, является ли matrix структурой указателя на структуру) вместо matrix[i][j].

Редактировать: Я действительно думал об одном полезном свойстве реализации ваших матриц как массивов указателей строк - оно делает перестановку строк тривиальной операцией. Если ваши алгоритмы должны выполнять много перестановок строк, это может быть полезно. Обратите внимание, что выгода не будет большой для маленьких матриц, и перестановка столбцов не может быть оптимизирована таким образом.

1 голос
/ 16 октября 2010

В C ++ Вы должны передать указатель по ссылке :)

Allocate2D(double**& matrix...)

Что касается того, что происходит - у вас есть указатель, равный NULL, вы передаете копию этого указателя в функцию, которая выделила память, и инициализирует копию вашего указателя с адресом вновь выделенной памяти, но ваш оригинал указатель остается NULL. Что касается бесплатного, вам не нужно передавать по ссылке, поскольку релевантным является только значение указателя. НТН

Поскольку в C нет ссылок, вы можете передать указатель, то есть

Allocate2D(double*** pMatrix...)
{
   *pMatrix = malloc(...)
}

и позже звоните как

Allocate2D(&matrix ...)
...