Вы обвиваетесь вокруг оси (путаете себя), объединяя распределение и преобразование в понижение в одну void to_lower(char ** strings)
функцию.Как вы обнаружили, если вы хотите дважды вызывать функцию для одного и того же объекта, вы должны free
выделить память, выделенную для хранения строчных букв между вызовами, - но тогда вы потеряли указатели на исходные строки..
Хотя нет ничего плохого в объединении нескольких операций в функции, вам все же нужно отступить и убедиться, что то, что вы делаете, имеет смысл и не вызовет больше проблем, чем решает.
Ваше выделение и копирование strings
требуется перед изменением строк, содержащихся в strings
, поскольку вы инициализируете strings
как массив указателей на строковые литералы .String-literal являются неизменяемыми (на всех, кроме очень немногих системах), созданными в постоянной памяти (обычно это раздел .rodata
исполняемого файла).Попытка изменить строковый литерал почти гарантирует гарантию SegFault (на всех, кроме странных систем)
Кроме того, как вы собираетесь вернуть исходные строки, если вы уже перезаписали адреса указателей наоригиналы с указателями на выделенную память, содержащие строчные буквы?(не говоря уже о головной боли при отслеживании, которую вы создаете, заменяя указатели на литералы указателями на выделенную память, если это нормально, free
эти указатели).
Здесь гораздо лучше оставить вас оригинальными strings
нетронутым и просто выделите копию оригинала (выделив указатели, в том числе один для значения часового, и выделив хранилище для каждой из исходных строк, а затем скопируйте исходные строки перед преобразованием в нижний регистр. Это решает вашу проблему с утечками памятиа также проблема с потерей ваших оригинальных указателей на строковые литералы. Вы можете освободить строчные буквы по мере необходимости, и вы всегда можете сделать еще одну копию оригиналов для отправки в вашу функцию преобразования снова.
Так как быВы собираетесь реализовать это? Самый простой способ - просто объявить указатель на указатель на символ (например, двойной указатель), которому вы можете назначить блок памяти для любого количества указателей, которые вам нравятся.этот случай просто выделитьстолько же указателей, сколько у вас в массиве strings
, например:
char *strings[] = {"Hello", "Zerotom", "new", NULL };
size_t nelem = *(&strings + 1) - strings; /* number of pointers */
/* declare & allocate nelem pointers */
char **modstrings = malloc (nelem * sizeof *modstrings);
if (!modstrings) { /* validate EVERY allocation */
perror ("malloc-modstrings");
}
( примечание: , вы также можете использовать sizeof strings / sizeof *strings
для получения количества элементов)
Теперь, когда у вас modstrings
назначен блок памяти, содержащий то же количество указателей, что и в strings
, вы можете просто выделить блоки памяти, достаточные для хранения каждого из строковых литералов и присвоения начальногоадрес для каждого блока в последовательных указателях в modstrings
, устанавливая последний указатель NULL
в качестве вашего стража, например
void copy_strings (char **dest, char * const *src)
{
while (*src) { /* loop over each string */
size_t len = strlen (*src); /* get length */
if (!(*dest = malloc (len + 1))) { /* allocate/validate */
perror ("malloc-dest"); /* handle error */
exit (EXIT_FAILURE);
}
memcpy (*dest++, *src++, len + 1); /* copy *src to *dest (advance) */
}
*dest = NULL; /* set sentinel NULL */
}
( note: , передав параметр src
как char * const *src
вместо просто char **src
, вы можете указать компилятору, что src
не будет изменен, что позволит дальнейшей оптимизации компилятором.restrict
было бы похоже, но это обсуждение оставлено на другой день)
Ваша to_lower
функция затем уменьшается до:
void to_lower (char **strings) {
while (*strings) /* loop over each string */
for (char *p = *strings++; *p; p++) /* loop over each char */
*p = tolower (*p); /* convert to lower */
}
Для удобства, так как вы знаете, что вы захотитечтобы скопировать strings
в modstrings
перед каждым вызовом to_lower
, вы можете объединить обе функции в одну оболочку (что имеет смысл объединять), например,
void copy_to_lower (char **dest, char * const *src)
{
copy_strings (dest, src); /* just combine functions above into single */
to_lower (dest);
}
(можно даже добавитьprint_array
и free_strings
выше, а также, если вы всегда хотите выполнять эти операции в одном вызове - подробнее позже)
Между каждым copy_to_lower
и print_array
из modstrings
вам нужно будет освободить память, назначенную каждому указателю, чтобы не допустить утечки памяти при повторном вызове copy_to_lower
.Простая функция free_strings
может быть такой:
void free_strings (char **strings)
{
while (*strings) { /* loop over each string */
free (*strings); /* free it */
*strings++ = NULL; /* set pointer NULL (advance to next) */
}
}
Теперь вы можете выделять, копировать, преобразовывать в нижние, печатать и освобождать столько раз, сколько захотите в main()
.Вы просто будете делать повторные звонки на:
copy_to_lower (modstrings, strings); /* copy_to_lower to modstrings */
print_array (modstrings); /* print modstrings */
free_strings (modstrings); /* free strings (not pointers) */
copy_to_lower (modstrings, strings); /* ditto */
print_array (modstrings);
free_strings (modstrings);
...
Теперь напомним, что вы освобождаете хранилище для каждой строки , когда вызываете free_strings
, но вы покидаете блок памяти, содержащий указатели, назначенные для modstrings
.Таким образом, чтобы заполнить вашу свободную память, которую вы выделили, не забудьте освободить указатели, например,
free (modstrings); /* now free pointers */
Поместив все это в пример, вы можете сделать следующее:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
void print_array (char **strings)
{
while (*strings)
printf ("%s, ", *strings++);
putchar ('\n');
}
void free_strings (char **strings)
{
while (*strings) { /* loop over each string */
free (*strings); /* free it */
*strings++ = NULL; /* set pointer NULL (advance to next) */
}
}
void copy_strings (char **dest, char * const *src)
{
while (*src) { /* loop over each string */
size_t len = strlen (*src); /* get length */
if (!(*dest = malloc (len + 1))) { /* allocate/validate */
perror ("malloc-dest"); /* handle error */
exit (EXIT_FAILURE);
}
memcpy (*dest++, *src++, len + 1); /* copy *src to *dest (advance) */
}
*dest = NULL; /* set sentinel NULL */
}
void to_lower (char **strings) {
while (*strings) /* loop over each string */
for (char *p = *strings++; *p; p++) /* loop over each char */
*p = tolower (*p); /* convert to lower */
}
void copy_to_lower (char **dest, char * const *src)
{
copy_strings (dest, src); /* just combine functions above into single */
to_lower (dest);
}
int main(void) {
char *strings[] = {"Hello", "Zerotom", "new", NULL };
size_t nelem = *(&strings + 1) - strings; /* number of pointers */
/* declare & allocate nelem pointers */
char **modstrings = malloc (nelem * sizeof *modstrings);
if (!modstrings) { /* validate EVERY allocation */
perror ("malloc-modstrings");
}
copy_to_lower (modstrings, strings); /* copy_to_lower to modstrings */
print_array (modstrings); /* print modstrings */
free_strings (modstrings); /* free strings (not pointers) */
copy_to_lower (modstrings, strings); /* ditto */
print_array (modstrings);
free_strings (modstrings);
copy_to_lower (modstrings, strings); /* ditto */
print_array (modstrings);
free_strings (modstrings);
copy_to_lower (modstrings, strings); /* ditto */
print_array (modstrings);
free_strings (modstrings);
free (modstrings); /* now free pointers */
}
Пример использования / вывода
$ ./bin/tolower_strings
hello, zerotom, new,
hello, zerotom, new,
hello, zerotom, new,
hello, zerotom, new,
Проверка использования памяти / ошибок
В любом написанном вами коде, который динамически выделяет память, у вас есть2 обязанности в отношении любого выделенного блока памяти: (1) всегда сохраняют указатель на начальный адрес для блока памяти, поэтому (2) он может быть освобожден , когда он больше не нужен.
Крайне важно, чтобы вы использовали программу проверки ошибок памяти, чтобы убедиться, что вы не пытаетесь получить доступ к памяти или писать за пределами / за пределами выделенного блока, пытаться прочитатьили основать условный переход на неинициализированном значении и, наконец, подтвердить, что вы освобождаете всю выделенную память.
Для Linux valgrind
является нормальным выбором.Для каждой платформы есть похожие проверки памяти.Все они просты в использовании, просто запустите вашу программу через него.
$ valgrind ./bin/tolower_strings
==5182== Memcheck, a memory error detector
==5182== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==5182== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==5182== Command: ./bin/tolower_strings
==5182==
hello, zerotom, new,
hello, zerotom, new,
hello, zerotom, new,
hello, zerotom, new,
==5182==
==5182== HEAP SUMMARY:
==5182== in use at exit: 0 bytes in 0 blocks
==5182== total heap usage: 13 allocs, 13 frees, 104 bytes allocated
==5182==
==5182== All heap blocks were freed -- no leaks are possible
==5182==
==5182== For counts of detected and suppressed errors, rerun with: -v
==5182== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Всегда подтверждайте, что вы освободили всю выделенную память и нет ошибок памяти.
Теперь этоэто длинный пост, но вы добились прогресса в изучении динамического распределения.Вам потребуется некоторое время, чтобы все это переварить, но если у вас есть дополнительные вопросы, просто дайте мне знать.