gpt4 book ai didi

c - 就地修改数组并了解其内存分配

转载 作者:太空宇宙 更新时间:2023-11-04 07:44:39 24 4
gpt4 key购买 nike

我有以下两个函数,它们接受字符串数组并使其小写(就地)--

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

void to_lower(char ** strings) {

char * original_string;
char * lower_string;

for (int i=0; (original_string=strings[i]) != NULL; i++) {
lower_string = malloc(strlen(original_string + 1) * sizeof(char));
for (int j=0; j<=strlen(original_string); j++) {
lower_string[j] = tolower(original_string[j]);
}
strings[i]=lower_string;
}
}


int main(void) {
char * strings[] = {"Hello", "Zerotom", "new", NULL };
to_lower(strings);
to_lower(strings); // this and successive calls won't change
to_lower(strings); // anything but are here to try and understand malloc
to_lower(strings);
to_lower(strings);
return 0;
}

在调用 main之前的 to_lower函数的开头,消耗了多少内存?我猜是字符数组的16个字节(15个字符加上结尾的1个空字节)。
to_lower运行5次之后和函数返回之前,消耗了多少内存?我应该在哪里“空闲”—使用传递到函数中的字符串(我的想法是每次复制/小写字符串时都调用 malloc,它会创建额外的内存,但永远不会释放任何内容。
to_lower函数看起来是否正常,或者如何修改它以使其当前不泄漏内存?

最佳答案

通过将分配和转换组合为一个void to_lower(char ** strings)函数,您将自己包裹在轴上(使自己感到困惑)。正如您所发现的,如果您想在同一个对象上调用函数两次,那么您必须free分配给在两次调用之间保存小写字符串的内存,但是您已经丢失了指向原始字符串的指针。。
虽然在一个函数中组合多个操作没有错,但是您必须后退一步,确保您所做的事情有意义,并且不会导致比它解决的问题更多的问题。
在修改strings中包含的字符串之前,需要分配和复制strings,因为您将strings初始化为指向字符串文本的指针数组。字符串文本是不可变的(除了极少数系统外),它是在只读内存(通常是可执行文件的.rodata部分)中创建的。尝试修改字符串文字几乎可以保证SegFault(除了一些古怪的系统之外)
此外,如果您已经用保存小写结果的分配内存指针覆盖了指向原始字符串的指针地址,您将如何获取原始字符串?(更不用说当可以free这些指针时,用指向已分配内存的指针替换指向文本的指针会产生跟踪问题)。
在这里,最好保持原始值strings不变,只需分配原始值的副本(通过分配指针,包括sentinel值的指针,并为每个原始字符串分配存储空间,然后在转换为小写之前复制原始字符串)。这解决了内存泄漏的问题以及丢失字符串文本的原始指针的问题。您可以根据需要释放小写字符串,并且始终可以制作原件的另一个副本以再次发送到转换函数。
那么,你将如何着手实施这个计划呢?最简单的方法就是声明一个指向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中的连续指针,将最后一个指针设置为哨兵,例如。
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 */
}

(注意:通过将 NULL参数传递为 src而不仅仅是 char * const *src,您可以向编译器指示不会更改 char **src,从而允许编译器进行进一步的优化。 src应该是类似的,但这一讨论将留待另一天进行)
然后,您的 restrict功能减少到:
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);
}

(如果您总是想在单个调用中执行这些操作,甚至还可以添加上面的 to_lowerprint_array——稍后再添加)
free_strings的每个 copy_to_lowerprint_array之间,需要释放分配给每个指针的存储空间,以便再次调用 modstrings时不会泄漏内存。一个简单的 copy_to_lower函数可以是:
void free_strings (char **strings)
{
while (*strings) { /* loop over each string */
free (*strings); /* free it */
*strings++ = NULL; /* set pointer NULL (advance to next) */
}
}

现在,您可以在 free_strings中任意多次分配、复制、转换为lower、打印和释放。您只需重复呼叫:
    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);
...

现在回想一下,当您调用 main()时,您正在释放每个字符串的存储空间,但是您将留下包含分配给 free_strings的指针的内存块。因此,要完成所有已分配内存的释放,不要忘记释放指针,例如。
    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,

内存使用/错误检查
在动态分配内存的任何代码中,对于任何分配的内存块,您都有两个职责:(1)始终保留指向内存块起始地址的指针,以便(2)在不再需要时可以释放它。
必须使用内存错误检查程序,以确保您不会尝试访问内存或写入超出或超出已分配块的界限,尝试读取未初始化值或将条件跳转基于未初始化值,最后确认您释放了已分配的所有内存。
对于Linux modstrings是正常的选择。每个平台都有类似的内存检查程序。它们都很容易使用,只要运行你的程序就可以了。
$ 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)

始终确认已释放所有已分配的内存,并且没有内存错误。
现在这是一个很长的帖子,但是你已经在学习动态分配方面取得了进展。你需要一段时间才能消化,但如果你还有其他问题,请告诉我。

关于c - 就地修改数组并了解其内存分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58127467/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com