Моя программа достаточно хороша для моего задания, но я знаю, что это не хорошо
Я только начинаю задание для uni, и он поднял вопрос для меня.
Я не понимаю, как вернуть строку из функции без утечки памяти.
char* trim(char* line) {
int start = 0;
int end = strlen(line) - 1;
/* find the start position of the string */
while(isspace(line[start]) != 0) {
start++;
}
//printf("start is %d\n", start);
/* find the position end of the string */
while(isspace(line[end]) != 0) {
end--;
}
//printf("end is %d\n", end);
/* calculate string length and add 1 for the sentinel */
int len = end - start + 2;
/* initialise char array to len and read in characters */
int i;
char* trimmed = calloc(sizeof(char), len);
for(i = 0; i < (len - 1); i++) {
trimmed[i] = line[start + i];
}
trimmed[len - 1] = '\0';
return trimmed;
}
как вы можете видеть, я возвращаю указатель на char, который является массивом. Я обнаружил, что если бы я попытался сделать массив "обрезанный" чем-то вроде:
char trimmed[len];
тогда компилятор будет вызывать сообщение о том, что в этой строке ожидалась константа. Я предполагаю, что это означает, что по какой-то причине вы не можете использовать переменные в качестве длины массива при инициализации массива, хотя что-то говорит мне, что это невозможно.
Поэтому вместо этого я создал свой массив, выделив некоторую память указателю char.
Я понимаю, что эта функция, вероятно, неоптимальна для того, что она пытается сделать, но я действительно хочу знать:
-
Можно ли инициализировать массив с помощью переменной, чтобы объявить длину, например:
char обрезано [len];
-
Если бы у меня был массив такого типа (char trimmed []), он имел бы тот же тип возврата, что и указатель на char (т.е. char *).
-
Если я создаю свой массив, выделив некоторую память и выделив ее указателю char, как мне освободить эту память. Мне кажется, что как только я верну этот массив, я не могу получить доступ к нему, чтобы освободить его, поскольку это локальная переменная.
Ответы
Ответ 1
Чтобы адресовать (3) - вы можете free
вновь выделенную строку из вашего кода вызова, как только вы закончите с ней:
char* tmp = trim(myline);
if (tmp != NULL) {
....
free( tmp );
}
Но это накладывает нагрузку на вызывающего, чтобы не забыть освободить память. Поэтому вместо этого вы можете передать выделенный буфер и размер буфера trim()
, например:
void trim(char* line, char *trimmed_buf, int trimmed_buf_len){ ... }
Нейл отлично справился с вашими другими вопросами. В основном объявление массива char trimmed[len];
объявит локальную переменную в стек , поэтому, если синтаксически корректно возвращать char *
в эту память, то местоположение памяти, на которое оно указывает, больше не будет действительный.
Ответ 2
Чтобы ответить на ваши конкретные вопросы, синтаксис:
char trimmed[len];
где len
- переменная, разрешена только на C99, а не на C89 или на С++. Тип возврата действительно был бы char *
, но возврат локальной переменной trimmed
приведет к поведению undefined, поэтому не делайте этого. И если вы распределяете массив динамически в функции с помощью calloc
и возвращаете его, то вызывающая функция освобождает его, используя указатель, возвращаемый функцией.
Ответ 3
Что касается динамической калибровки объявления массива типа char trimmed[len];
, то самая новая версия стандарта C (ISO/IEC 9899: 1999) позволяет, но для этой функции это не помогло бы вообще. Переменная trimmed
имеет свою область действия внутри функции trim
, и она выделяется в стеке. Поэтому, если вы поместите return trimmed;
в свой код, вы вернете указатель на переменную в стеке, а часть стека, в которой находится эта переменная, будет выпущена в тот момент, когда функция вернется, так что это не получится так хорошо...
Ответ 4
Передайте вызывающему абоненту указатель на область памяти (максимальную длину) для функции. Таким образом, вызывающий абонент будет нести ответственность за выделение (и освобождение памяти) памяти, а функция будет обеспечивать только выход, ограниченный буфером, переданным в функцию.
Уверен, что человек, использующий эту функцию, все равно может испортить вещи (указав длину, которая не имеет никакого отношения к фактическому размеру буфера, но тогда вы можете правильно аргументировать ее ошибку вызывающего абонента, а не эту ошибку функции.
Ответ 5
Помимо решений на С++, таких как использование std::string
, вы всегда можете выделить массив заданного размера и передать размер в качестве параметра, а массив как параметр по ссылке или как указатель?
Таким образом, данные распределяются в той же области видимости и утечки памяти.
Затем вы всегда можете освободить память после вызова. Это означает, что код, ответственный за его создание, и код, ответственный за уничтожение данных, не совпадают, что может быть предшественником ошибок и ошибок.
Ответ 6
Выделение и удаление вне функции:
Выделите его из стека с char обрезанным [SIZE] и передайте его функции или из кучи с calloc и передайте ее функции.
Выделение внутри функции и удаление снаружи:
Вы выделяете его внутри функции с помощью calloc, но вызывающий должен освободить ее, используя бесплатный.
Ответ 7
Hm, ну, ответы на ваши вопросы:
- да, массив будет выделен в стеке, а не в кучу и будет освобожден при возврате функции.
- да, char [] в основном эквивалентен char *. Лучше держать их отдельно, так как существует семантическая разница.
- Используя любой указатель на память, вы можете использовать бесплатный. В вашем случае вы освободите функцию возврата. Это считается довольно плохой формой и склонностью к ошибкам. Обычно вы хотите, чтобы allocat'er был свободным() er. Возможно, вы могли бы пройти в буфер для нового пространства, или вызывающий объект мог согласиться с содержимым
char * line
, написанным поверх существующего содержимого. Это работает, поскольку trim() всегда удаляет только вещи. Я думаю, что пройденный в буфере будет работать чаще, и это хорошая вещь, чтобы привыкнуть.
Что касается вашей функции, рассмотрите возможность использования memcpy() или его кузенов для копирования байтов в буфер char и из него.