通过将分配和转换组合到一个单一的void to_lower(char ** strings) 函数中,您将自己包裹在轴上(让自己感到困惑)。正如您所发现的,如果您想在同一个对象上调用该函数两次,您必须 free 在两次调用之间分配用于保存小写字符串的内存 - 但是您丢失了指向原始字符串的指针..
虽然在一个函数中组合多个操作并没有错,但您必须退后一步,确保您所做的事情是有意义的,并且不会导致比它解决的问题更多的问题。
在修改strings 中包含的字符串之前,需要分配和复制strings,因为您将strings 初始化为指向字符串文字的指针数组。字符串文字是在只读内存(通常是可执行文件的.rodata 部分)中创建的不可变的(在除少数系统之外的所有系统上)。尝试修改字符串文字几乎可以保证 SegFault(在除少数古怪系统之外的所有系统上)
此外,如果您已经用指向保存小写结果的已分配内存的指针覆盖了指向原始字符串的指针地址,您将如何取回原始字符串? (更不用说你通过用指向已分配内存的指针替换指向文字的指针所造成的跟踪头痛,只要free 这些指针是可以的)。
这里最好保留原始strings 原样并简单地分配原始副本(通过分配指针,包括一个用于标记值的指针,并为每个原始字符串分配存储空间,然后复制原始转换为小写之前的字符串。这解决了您的内存泄漏问题以及丢失指向字符串文字的原始指针的问题。您可以根据需要释放小写字符串,并且您可以随时制作另一个原件副本发送到您的再次转换函数。
那么您将如何实施呢?最简单的方法就是声明一个 pointer to pointer to char(例如双指针),你可以为你喜欢的任意数量的指针分配一块内存。在这种情况下,只需分配与 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 */
}
(注意:通过将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 */
}
为方便起见,由于您知道在每次调用 to_lower 之前都希望将 strings 复制到 modstrings,因此您可以将这两个函数组合到一个包装器中(组合起来确实有意义),例如
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 和modstrings 的每个copy_to_lower 和print_array 之间,您需要释放分配给每个指针的存储空间,这样当您再次调用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)
始终确认您已释放已分配的所有内存并且没有内存错误。
这是一篇很长的帖子,但您在学习动态分配方面取得了进展。您需要一些时间来消化所有内容,但如果您还有其他问题,请告诉我。