C когда выделять и освобождать память - до вызова функции, после вызова функции ... и т. Д. - PullRequest
3 голосов
/ 08 июня 2010

Я работаю с моим первым прямым проектом на C, и я давно уже работал над C ++.Таким образом, все управление памятью немного размыто.

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

int validate_input(const char *input_line, char** out_value){

    int ret_val = 0; /*false*/
    int length = strlen(input_line);
    out_value =(char*) malloc(sizeof(char) * length + 1);

    if (0 != length){

        int number_found = 0;
        for (int x = 0; x < length; x++){

            if (input_line[x] != ' '){ /*ignore space*/

                /*get the character*/
                out_value[number_found] = input_line[x];
                number_found++; /*increment counter*/
            }
        }
        out_value[number_found + 1] = '\0';

        ret_val = 1;
    }

    return ret_val;

}

Вместо того, чтобы выделять память внутри функции для out_value , я должен сделать это до вызова функции и всегда ожидать вызывающеговыделить память перед переходом в функцию?Как правило, должна ли какая-либо память, выделенная внутри функции, всегда освобождаться до того, как функция вернется?

Ответы [ 8 ]

7 голосов
/ 08 июня 2010

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

1 / Выделите память, когда вам это нужно, как только вы знаете, что вам нужно. Это позволит вам фиксировать ошибки нехватки памяти, прежде чем делать слишком много работы.

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

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

int validate_input (const char *input_line, char **out_value_ptr) {
    : :
    *out_value_ptr =(char*) malloc(length + 1); // sizeof(char) is always 1
    : :
    (*out_value_ptr)[number_found] = input_line[x];
    : :

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

Но имейте в виду, что вы можете разрешить оба варианта. Другими словами, если функции передается char**, который указывает на NULL, пусть она выделяет память. В противном случае он может предположить, что вызывающая сторона сделала это:

    if (*out_value_ptr == NULL)
        *out_value_ptr =(char*) malloc(length + 1);
2 голосов
/ 08 июня 2010

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

Если вы сделаете это, вам нужно объявить выходной параметр иначе (либо как ссылку в стиле C ++, либо как char ** в стиле C. Как определено, указатель будет существовать тольколокально и будет протекать.

2 голосов
/ 08 июня 2010

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

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

1 голос
/ 08 июня 2010

Вот несколько рекомендаций по распределению памяти:

  1. Выделяют только при необходимости.
  2. Огромные объекты должны быть динамически выделены. Большинство реализаций не хватает локального хранилища (стек, глобальная / программная память).
  3. Установите правила владения для выделенный объект. Владелец должен быть ответственный за удаление.

Рекомендации по освобождению памяти:

  1. Удалить, если выделено, не удалять объекты или переменные, которые не были динамически распределяется.
  2. Удалить, когда он больше не используется. Смотрите правила владения вашим объектом.
  3. Удалить до выхода из программы.
1 голос
/ 08 июня 2010

Типичная практика - выделять память вне для out_value и передавать длину блока в октетах в функцию с указателем. Это позволяет пользователю решать, как он хочет выделить эту память.

Одним из примеров этого шаблона является функция recv , используемая в сокетах:

 ssize_t recv(int socket, void *buffer, size_t length, int flags);
0 голосов
/ 09 июня 2010

Некоторые грубые рекомендации для рассмотрения:

  1. Предпочитают, чтобы вызывающая сторона выделяла память. Это позволяет ему контролировать, как / где эта память выделяется. Вызов malloc() непосредственно в вашем коде означает, что ваша функция диктует политику памяти.
  2. Если невозможно заранее определить, сколько памяти может понадобиться, вашей функции может потребоваться обработать выделение.
  3. В тех случаях, когда вашей функции требуется выделение, подумайте о том, чтобы позволить вызывающей стороне передать обратный вызов распределителя, который она использует вместо прямого вызова malloc. Это позволяет вашей функции распределять, когда ей нужно и столько, сколько нужно, но позволяет вызывающей стороне контролировать, как и где эта память выделяется.
0 голосов
/ 08 июня 2010

Во-первых, приведенный вами пример кода , а не ANSI C. Он больше похож на C ++. В C нет оператора «<<», который работает как выходной поток для чего-то, называемого «cout». </p>

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

Далее, вопрос о том, следует ли выделять / освобождать до или после вызова функции, зависит от роли функции. У вас может быть функция, задачей которой является выделение и инициализация некоторых данных, а затем возвращение их вызывающей стороне, в этом случае она должна malloc(), а вызывающая сторона должна free(). Однако, если вы просто выполняете некоторую обработку с помощью пары буферов, например, вы можете предпочесть, чтобы вызывающая сторона выделяла и освобождала. Но для вашего случая, поскольку ваша функция «validate_input», похоже, не делает ничего, кроме копирования строки без пробела, вы можете просто набрать malloc() в функции и оставить ее вызывающей стороне. Хотя, поскольку в этой функции вы просто выделяете тот же размер, что и вся входная строка, кажется, что вы могли бы также иметь вызывающего для всего этого. Все действительно зависит от вашего использования.

Просто убедитесь, что вы не теряете указатели, как в этом примере

0 голосов
/ 08 июня 2010

В этом примере вы не должны ни освобождать, ни выделять память для out_value. Он набирается как char*. Следовательно, вы не можете «вернуть» новую память вызывающей функции. Для этого нужно взять char**

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

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