Nano Hash - криптовалюты, майнинг, программирование

C: Правильное освобождение памяти многомерного массива

Допустим, у вас есть следующий код ANSI C, который инициализирует многомерный массив:

int main()
{
      int i, m = 5, n = 20;
      int **a = malloc(m * sizeof(int *));

      //Initialize the arrays
      for (i = 0; i < m; i++) { 
          a[i]=malloc(n * sizeof(int));
      }

      //...do something with arrays

      //How do I free the **a ?

      return 0;
}

После использования **a как мне правильно освободить его из памяти?


[Обновление] (решение)

Благодаря Тиму (и другим) ответу, теперь я могу сделать такую ​​функцию, чтобы освободить память из моего многомерного массива:

void freeArray(int **a, int m) {
    int i;
    for (i = 0; i < m; ++i) {
        free(a[i]);
    }
    free(a);
}

  • Придирка к терминологии: это не то, что C обычно называет многомерным массивом. Это единственный способ использовать синтаксис a[i][j], при этом оба измерения остаются неизвестными во время компиляции. Другой вид многомерного массива — это массив массивов, вместо этого массива указателей на (первые элементы) массивов. 14.11.2009

Ответы:


1

Хорошо, есть большая путаница, объясняющая, в каком именно порядке должны быть необходимые free() вызовы, поэтому я попытаюсь прояснить, чего люди пытаются добиться и почему.

Начнем с основ, чтобы освободить память, которая была выделена с помощью malloc(), вы просто вызываете free() точно с указателем, который вам дал malloc(). Итак, для этого кода:

int **a = malloc(m * sizeof(int *));

вам нужно соответствие:

free(a);

и для этой строки:

a[i]=malloc(n * sizeof(int));

вам нужно соответствие:

free(a[i]);

внутри аналогичного цикла.

Что усложняется, так это порядок, в котором это должно происходить. Если вы вызываете malloc() несколько раз, чтобы получить несколько разных фрагментов памяти, в общем случае не имеет значения, в каком порядке вы вызываете free(), когда вы с ними покончили. Однако порядок здесь важен по очень конкретной причине: вы используете один фрагмент памяти malloced для хранения указателей на другие фрагменты памяти malloced. Поскольку вы не должны не пытаться читать или записывать память после того, как вы вернули ее с помощью free(), это означает, что вам придется освобождать фрагменты с их указателями, хранящимися в a[i] прежде чем вы освободите сам фрагмент a. Отдельные фрагменты с указателями, хранящиеся в a[i], не зависят друг от друга, и поэтому могут быть freed в любом порядке.

Итак, сложив все это вместе, мы получаем следующее:

for (i = 0; i < m; i++) { 
  free(a[i]);
}
free(a);

И последний совет: при вызове malloc() рассмотрите возможность их изменения:

int **a = malloc(m * sizeof(int *));

a[i]=malloc(n * sizeof(int));

to:

int **a = malloc(m * sizeof(*a));

a[i]=malloc(n * sizeof(*(a[i])));

Что это делает? Компилятор знает, что a — это int **, поэтому он может определить, что sizeof(*a) совпадает с sizeof(int *). Однако, если позже вы передумаете и захотите chars, shorts или longs или что-то еще в своем массиве вместо ints, или вы адаптируете этот код для последующего использования в чем-то другом, вам придется изменить только одну оставшуюся ссылку на int в первую процитированную строку выше, и все остальное автоматически встанет на свои места. Это исключает вероятность незамеченных ошибок в будущем.

Удачи!

14.11.2009
  • +1 Отличный ответ; спасибо за объяснение проблемы с «обратным порядком», а также за пункт о выполнении sizeof(*a) 14.11.2009
  • Кроме того, поправьте меня, если я ошибаюсь, но правильно ли я скажу, что sizeof(*a[i]) эквивалентно вашему sizeof(*(a[i])), поскольку запись массива [] имеет более высокий приоритет, чем *? 14.11.2009
  • Нет, я думаю, ты прав. en.wikipedia.org/wiki/ Однако я работаю над Эмпирическое правило гласит, что если мне придется искать его, вероятно, другим, читающим мой код, также придется его искать, поэтому, чтобы избавить их (и меня позже) от проблем, явное использование скобок помогает прояснить ситуацию и сэкономить время. Хотя это всего лишь личное предпочтение. 14.11.2009

  • 2

    Отменить именно то, что вы выделили:

      for (i = 0; i < m; i++) { 
          free(a[i]);
      }
      free(a);
    

    Обратите внимание, что вы должны делать это в обратном порядке, в котором вы изначально выделили память. Если вы сначала сделали free(a), то a[i] будет обращаться к памяти после того, как она будет освобождена, что является неопределенным поведением.

    14.11.2009
  • Сказать, что вы должны освобождать в обратном порядке, может ввести в заблуждение. Вам просто нужно освободить массив указателей после самих указателей. 14.11.2009
  • Разве это не другой способ сказать реверс? 14.11.2009
  • Я думаю, что @Andomar означает, что не имеет значения, в каком порядке вы освобождаете a [i], просто вы должны освободить их всех, прежде чем освобождать a. Другими словами, вы можете освободить от a[0] до a[m-1] или от [m-1] до a[0] или всех четных a[], за которыми следуют шансы. Но я также уверен, что @GregH не имеет в виду, что вам нужно делать a[] в обратном порядке, особенно учитывая его код. 14.11.2009

  • 3

    Вам нужно снова повторить массив и сделать столько освобождений, сколько mallocs для указанной памяти, а затем освободить массив указателей.

    for (i = 0; i < m; i++) { 
          free (a[i]);
    }
    free (a);
    
    14.11.2009

    4

    Пишите операторы распределения в обратном порядке, меняя имена функций, и все будет в порядке.

      //Free the arrays
      for (i = m-1; i >= 0; i--) { 
          free(a[i]);
      }
    
      free(a);
    

    Конечно, вам не нужно освобождать в том же самом обратном порядке. Вам просто нужно отслеживать освобождение одной и той же памяти ровно один раз и не «забывать» указатели на выделенную память (как это было бы, если бы вы сначала освободили a). Но освобождение памяти в обратном порядке — хороший пример решения последней проблемы.

    Как указано в комментариях litb, если выделение/освобождение имеет побочные эффекты (например, операторы new/delete в C++), иногда обратный порядок освобождения может быть более важным, чем в этом конкретном примере.

    14.11.2009
  • Почему вы должны изменить порядок в цикле? 14.11.2009
  • Поскольку a[1] был выделен после a[0], вы должны сначала освободить a[1]. 14.11.2009
  • Зачем вам нужно освобождать a[1] перед a[0] ? Это разные куски malloc, между ними нет никакой зависимости 14.11.2009
  • Кто сказал, что нужно? Я думаю, это просто выглядит... нормально. 14.11.2009
  • Поскольку free не имеет побочных эффектов в C (относительно вашего собственного программного кода), это не имеет большого значения. Правило «сделай это в обратном порядке» более важно для языков, которые имеют побочный эффект, связанный с free и alloc, например C++ с его деструкторами/конструкторами. 14.11.2009
  • Я почти уверен, что большинство людей выпустили бы массив вперед, а не назад. 14.11.2009
  • Обратный порядок освобождения элементов массива не требуется, это усложняет чтение и понимание кода. Обоснование этого шага полным нерелевантным примером (C++ new/delete) также не помогает. 14.11.2009
  • @Till: я ничего не оправдываю и надеюсь, что некоторые люди более гибко воспринимают код, который они читают. 14.11.2009

  • 5

    Я бы вызвал malloc() и free() только один раз:

    #include <stdlib.h>
    #include <stdio.h> 
    
    int main(void){
      int i, m = 5, n = 20;
      int **a = malloc( m*(sizeof(int*) + n*sizeof(int)) );
    
      //Initialize the arrays
      for( a[0]=(int*)a+m, i=1; i<m; i++ ) a[i]=a[i-1]+n;
    
      //...do something with arrays
    
      //How do I free the **a ?
      free(a);
    
      return 0;
    }
    14.11.2009
  • как это ответ на вопрос? 14.11.2009
  • Павел Швед написал правильный ответ. Я только что написал комментарий с некоторым кодом. 14.11.2009
  • Вы должны написать комментарии в поле комментариев вопроса. Он также поддерживает блоки кода. 14.11.2009
  • @litb: пожалуйста, скопируйте мой ответ в поле для комментариев к вопросу. Спасибо. 14.11.2009
  • Нет причин для отрицательного голосования. Я нахожу ответ на многие вопросы о SO: вы не должны этого делать, вместо этого сделайте это. Есть место для ответов на букву вопроса и ответа на смысл вопроса. 14.11.2009
  • Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

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

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..