Как правильно использовать strtok в C, чтобы не было утечки памяти?

Я немного смущен тем, что происходит, когда вы вызываете strtok на указателе char в C. Я знаю, что он изменяет содержимое строки, поэтому, если я вызову strtok для переменной с именем 'line', ее содержимое будет изменение. Предположим, что я следую ниже:

void function myFunc(char* line) {

    // get a pointer to the original memory block
    char* garbageLine = line;

    // Do some work
    // Call strtok on 'line' multiple times until it returns NULL
    // Do more work

    free(garbageLine);
}

Далее предположим, что 'строка' разделяется до того, как она будет передана myFunc. Должен ли я освобождать исходную строку после использования strtok или это делает работу для нас? Кроме того, что происходит, если "строка" не разделяется, и я пытаюсь использовать функцию выше? Безопасно ли вместо этого делать следующее? (Предположите, что программист не будет звонить бесплатно, если он знает, что строка не разделена)

Призвание

char* garbageLine = line;
myFunc(line);
free(garbageLine);

Определение функции

void function myFunc(char* line) {
    // Do some work
    // Call strtok on 'line' multiple times until it returns NULL
    // Do more work
}

Ответ 1

strtok() ничего не освободит, поскольку он не знает, где хранится строка. Это может быть в стеке или куче, он не знает и не заботится!:)

Безопаснее ли сделать следующее?

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

Дальнейшее чтение: strtok()

Ответ 2

В комментарии к вашему вопросу вы говорите, что вы "назовите strtok on" line "несколько раз, пока не вернете NULL". Это звучит так, как будто вы можете неправильно использовать strtok. В первый раз, когда вы его вызываете, вы должны называть его "строка" в качестве аргумента; при последующих вызовах вы должны передать ему NULL. В качестве примера возьмем следующее:

void function myFunc(char* line) {
  char *segment; // This will point at each delimited substring in turn.

  segment = strtok(line, " ");

  // Do something with segment.

  segment = strtok(NULL, " ");

  // Do something with the new segment.

  free(line);
}

Однако, как сказал DrTwox, ваш второй пример лучше - "строка" должна быть освобождена в том же контексте, что и malloced (или нет), поэтому вызов free() не принадлежит этой функции. И вам лучше зациклировать его - что-то вроде:

void function myFunc(char* line) {
  char *segment;

  segment = strtok(line, " ");

  while (segment != NULL) {
    // Do something with segment.

    segment = strtok(NULL, " ");
  }
}

Вызов выглядит так:

char *line = malloc(20*sizeof(char));

// Check that malloc succeeded here.
// Put some data into 'line'.

myFunc(line);

free(line);

// No 'garbageLine' required.

Способ, которым работает strtok, немного сложно объяснить, но у вас есть важные части - он не выделяет и не освобождает память. Вместо этого он работает, изменяя строку, которую вы передали ей.

Ответ 3

strtok больше не освобождает память, чем strlen. Зачем вам это ожидать? Какую память он мог бы освободить? Возможно, вы считаете, что strtok нужно освободить память, поскольку он хранит NUL, но содержимое памяти не имеет значения. Когда вы выделяете память, распределитель отслеживает размер выделенного вами блока, а весь блок освобождается при его освобождении.

Ответ 4

Что это связано с strtok()? Если вы выделяете память, вам нужно ее освободить. Там, где ваше приложение решает выделить и освободить память, зависит от вас. Но если вы передадите память на strtok(), это не имеет значения, насколько это возможно или когда выделена или освобождена память.

Ответ 5

Стоит объяснить, что strtok выполняет свою работу путем:

  1. возвращая указатели, которые указывают на оригинальную строку; а также

  2. заменяя каждый символ разделителя, который он находит, на NULL.

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

Ответ 6

До тех пор, пока вы используете только тип "char *", с помощью переполнения буфера невозможно выполнить любую операцию на 100%.

Что касается утечек памяти; если вы его выделите, лучше всего удалить его в том же блоке (если это возможно). Это означает, что если вы предоставляете реализацию strtok, она должна удалять любую память, которую он выделяет внутренне, но не удаляет любую память, переданную методу.

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

Чтобы выполнить "безопасную" обработку без переполнения буфера, вам также нужно иметь значение максимального значения. Например, strncpy (...) принимает параметр "максимальное количество символов". Это предотвращает некоторые виды атак, когда память "вниз по течению" от распределения строки может быть заполнена данными, которые позже интерпретируются как код какой-либо другой частью программы.

Это означает, что ваш myFunc(char* line) должен иметь подпись myfunc(char* line, int max_chars), и все ваши последующие операции должны быть гарантированы max_Chars + 1 объемом памяти для работы. Плюсом является сохранение конечного нуля, который может отсутствовать. Внутри вы должны убедиться, что все операции, такие как strcpy, работают только с первыми max_chars (strncpy делает это).

Это означает, что в конечном итоге вы всегда будете одобрять версию "n" (количество символов) строкового манипулятора для безопасности переполнения буфера. Поделитесь этим с сильной практикой "освобождение в блоке, которую вы распределите", и вы избежите 90% большинства ошибок программирования, связанных с строкой.

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