Правильный способ освобождения указателей при использовании метода цепочки - PullRequest
1 голос
/ 23 сентября 2019

В настоящее время я пишу несколько различных реализаций умножения матриц в c.Поэтому я написал вспомогательные функции для сложения и вычитания матриц, которые имеют следующую структуру:

int** m_sub(int** a, int** b, int size) {
    int **result = get_matrix(size);
    // calculate addition of a & b
    return result;

}

с get_matrix():

int** get_matrix(int size){
    int** m = malloc(size*sizeof(int));
    for (int i=0;i<size;i++){
        m[i] = malloc(size*sizeof(int));
    }
    return m;

}

Я разработал все функции для работы и возврата int**, потому что, насколько я понимаю, вы не можете возвращать двумерные массивы переменного размера в c.

Теперь программа работает нормально как есть, однако через запускПроверка памяти (Valgrind) Я обнаружил, что все возвращаемые значения из вызовов, например, m_add вызывают утечки памяти, потому что я использую возвращенное значение непосредственно в цепочке методов, и я не free() их, например:

m_copy(c_11,m_add(matmul_recursive(n/2, a,e),matmul_recursive(n/2, b, g), n/2), n/2);

Я знаю, что мог бы исправить это, присвоив каждому возвращенному int** переменную и затем вручную освободив ее.Однако это привело бы к огромному количеству временно назначенных переменных и, таким образом (возможно) менее читаемому коду.

Теперь мне было интересно, есть ли «правильный» способ сделать это в c?Или тот факт, что я возвращаю int** указатели для матриц, уже неудачный выбор дизайна?

Ответы [ 3 ]

1 голос
/ 23 сентября 2019

Проблема в том, что C не имеет понятия об автоматическом вызове деструктора, как C ++ как.Итак, AFAIK, вы, как программист, ответственны за освобождение всего, что было выделено.

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

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

0 голосов
/ 23 сентября 2019

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

Чтобы создать struct, который отвечает за хранение всех адресов созданных матриц (давайтеназовите это контейнером).

  1. Таким образом, ваши функции также получат указатель на этот объект контейнера матрицы.Когда вызывается get_matrix, адрес вновь выделенной матрицы добавляется в контейнер.

  2. Все, что вам нужно сделать в конце расчета, это правильно освободить контейнер.

Что-то вроде следующего (псевдокод ниже):

int** get_matrix(int size, struct MatricesContainer* cnt){ //takes the container
    int** m = malloc(size*sizeof(int));
    for (int i=0;i<size;i++){
        m[i] = malloc(size*sizeof(int));
    }
    vector_push_back(m, cnt); //push_the address in the container
    return m;
}

int** m_sub(int** a, int** b, int size, struct MatricesContainer* cnt) {
    int **result = get_matrix(size, cnt); //address of result is stored in cnt
    // calculate addition of a & b
    return result;
}

и его использование в обычном режиме, а затем уничтожение в функции

int heavy_calculation(...)
{
struct MatricesContainer* cnt
   .....
   m_copy(c_11,m_add(matmul_recursive(n/2, a,e, cnt),matmul_recursive(n/2, b, g,cnt),   n/2), n/2,cnt); 
   ...
   destroy_container(cnt);
}

destroy_container перебирает вектор матриц и освобождает их одну за другой.

Пожалуйста, обратите внимание, что, как указано в комментарии, get_matrix имеет проблемы.int** m = malloc(size*sizeof(int)); должно быть int** m = malloc(size*sizeof(int*));, так как вам нужно выделить место для указателей, а не для int s.

0 голосов
/ 23 сентября 2019

Фактически, вручную освобождая каждый указатель, это правильный способ сделать это в C, поскольку, как писал Серж, в C. автоматического вызова деструктора не существует, однако вы можете сделать это вручную и довольно красиво, не прибегая к динамическому стеку или сборщику мусора, подобному решениям.что, по моему мнению, обычно было бы огромным излишним.Так что без дальнейших церемоний.

  1. Назовите функцию создателя так, чтобы было ясно, что она выделяет память как create ... или allocate ... or new ... и not get.Давайте предположим создать / удалить номенклатуру здесь.Итак:

    int* create_matrix(int size);

Примечание. Я использую int*, а не int**.Это более удобно, не требует, чтобы вы делали все выделения второго измерения, вы просто выделяете один массив размером n*n.В памяти это одно и то же пространство.Однако вы можете придерживаться двух измерений, если вы предпочитаете.

Далее деструктор

void delete_matrix(int* matrix);

Теперь, когда у нас есть красивый синтаксис, мы модифицируем ваши прототипы операций.Вместо того, чтобы брать два массива и возвращать вновь выделенный, используйте первый аргумент.Ваша функция умножения, распределяющая данные, не годится, так как не интуитивно предполагать, что умножение распределяет новые данные, а не возвращает их по значению.Итак:

int* matmul_recursive(int* left, const int* right, int size);

Обратите внимание, что мы не будем изменять right, следовательно, const.Мы будем изменять left, следовательно, без констант.Теперь запишите умножение left и right и поместите результаты в left.Отсутствие дополнительного распределения означает отсутствие утечек.Функция должна возвращать left для удобства связанных вызовов.

Обратите внимание, что вы можете добавить int* create_matrix_copy(const int* source, int size); для клонирования матрицы.

Обратите также внимание, что вам обычно не нужны аргументыоперации после операций.Вас заинтересует полученная накопленная матрица.Посмотрите этот пример (используя имена ваших функций):

int* arg1 = create_matrix(n);
int* arg2 = create_matrix(n);
int* product1 = create_matrix(n);
int* product2 = create_matrix(n);
int* result = m_add(matmul_recursive(product1, arg1, n), matmul_recursive(product2, arg2, n), n);
delete_matrix(arg1);
delete_matrix(arg2);
delete_matrix(product2);
printf("%d", product1[0]);
printf("%d", result[0]); // result == product1
delete_matrix(product1);

Также используйте const везде, где это применимо, т.е. вы не меняете значение аргумента, heplps избегает ошибок.Также рассмотрите возможность использования unsigned int для размеров.

Также чаще, чем обычно, вы не будете выполнять много операций над одной матрицей, поэтому это будет более распространенным (например, 3D-преобразования):

int* result = create_matrix(n);
int* rotate = create_matrix(n);
int* translate = create_matrix(n);
int* scale = create_matrix(n);
matmul_recursive(matmul_recursive(matmul_recursive(result, rotate, n), translate, n), scale, n);
printf("%d", result[0]);
// Remember to delete all arguments.
...