【问题标题】:Any Downsides to this Method of String Retrieval?这种字符串检索方法有什么缺点吗?
【发布时间】:2012-08-22 14:44:57
【问题描述】:

不久前我在这个网站上看到了一个功能,我把它做了一些调整以供我使用。

它是一个使用 getc 和 stdin 检索字符串并精确分配包含该字符串所需的内存的函数。然后它只返回一个指向已分配内存的指针,该内存填充了所述字符串。

我的问题是这个函数有什么缺点(除了以后必须手动释放分配的内存)吗?你会做些什么来改进它?

char *getstr(void)
{
    char *str = NULL, *tmp = NULL;
    int ch = -1, sz = 0, pt = 0;

    while(ch)
    {
        ch = getc(stdin);
        if (ch == EOF || ch == 0x0A || ch == 0x0D) ch = 0;
        if (sz <= pt)
        {
            sz++; 
            tmp = realloc(str, sz * sizeof(char));
            if(!tmp) return NULL;
            str = tmp;
        }
        str[pt++] = ch;
    }

    return str;
}

在使用您的建议后,这里是我更新的代码,我决定只使用 256 字节作为缓冲区,因为此函数用于用户输入。

char *getstr(void)
{
    char *str, *tmp = NULL;
    int ch = -1, bff = 256, pt = 0;

    str = malloc(bff);
        if(!str) 
        {
            printf(\nError! Memory allocation failed!");
            return 0x00;
        }
    while(ch)
    {
        ch = getc(stdin);
        if (ch == EOF || ch == '\n' || ch == '\r') ch = 0;
        if (bff <= pt)
        {
            bff += 256; 
            tmp = realloc(str, bff);
            if(!tmp) 
            {
                free(str);
                printf("\nError! Memory allocation failed!");
                return 0x00;
            }
            str = tmp;
        }
        str[pt++] = ch;
    }
    tmp = realloc(str, pt);
    if(!tmp)
    {
        free(str);
        printf("\nError! Memory allocation failed!");
        return 0x00;
    }
    str = tmp;

    return str;
}

【问题讨论】:

  • 让我想起了这个:stackoverflow.com/a/8164021/714501
  • 是的!这正是我第一次看到这个函数的地方。
  • 您的修订版更加明智。您可能还想考虑为每个 realloc() 将 bff 乘以 2,请参阅 Qnan 和我在他的回答 cmets 中的讨论。尽管如此,如果您说这只是用于手动用户输入,那么您所拥有的一切都很好。

标签: c string dynamic input stdin


【解决方案1】:

IMO 过于节俭,并且犯了牺牲性能以节省无限量内存的错误,我认为这在大多数情况下毫无意义。像 realloc 这样的分配调用对系统来说可能很费力,在这里它是针对每个字节完成的。

最好只有一个本地缓冲区,比如 4KB 来读入,然后根据实际读入的长度分配返回字符串。请记住,普通系统上的堆栈*无论如何都是 4-8MB,无论您是否全部使用它。如果读取的字符串长度超过 4KB,您可以编写一个类似的循环来分配并复制到返回字符串中。所以一个类似的想法,但是堆分配将每 4096 个字节而不是每个字节发生一次,所以,例如,你有 4096 的初始缓冲区,当它用完时你 malloc 4096 作为返回字符串并复制,继续读入缓冲区(从头开始),如果再读取 1000 个字节,则重新分配到 5097 并返回。

我认为对于初学者来说,通过逐字节处理来最小化堆分配是一个常见的错误。即使 KB by KB 也有点小;系统以页 (4 KB) 为单位分配,您不妨调整一下。

*为函数内部的本地存储提供的内存。

【讨论】:

  • 在大多数系统上,堆栈分配了几兆字节的地址空间,但实际分配的内存将是当前大小,四舍五入为整数页数(例如,4KB 块)。
  • @JerryCoffin:当然,OP 应该了解虚拟地址空间与真实内存之间的差异。但是,我认为并非所有操作系统都以相同的方式处理此问题:其中一些将虚拟地址空间作为具体承诺,因此如果操作系统为进程提供 4MB 的地址空间,它还会留出 4MB 的实际内存来支持该承诺(尽管没有一个是在虚拟 真实映射的意义上分配的)。其他人则没有(这就是为什么默认情况下,malloc 或 realloc 在 linux 上永远不会返回 null)。等等。只是想提出一些基本原则;)
【解决方案2】:

是的,主要问题是realloc 非常慢,并且为每个字符重复调用它通常是个坏主意。

尝试分配固定数量的内存,例如N=100 字符,当您需要更多时,获得类似2*N,然后是4*N 等等。您最多只会超支两倍的内存,但会节省很多的运行时间。

【讨论】:

  • 更好的主意:分配一个大缓冲区,然后如果您需要超出它,使用 realloc 将您的空间加倍。如果你真的想修剪多余的存储空间,你可以malloc、copy、free,甚至最后重新分配。
  • 当您说主要问题时,您是在暗示还有其他问题吗?你会有什么不同的做法?
  • 一开始我考虑分配一个大的内存块,但由于某种原因,我发现分配比需要更多的内存的想法有点令人不安,但确实在最后调用 realloc 一次会比调用多个 realloc 实例更有效。
  • @KeithMiller 除此之外,读取 CR+LF 样式文档时不会在行间返回 NULL 吗?
  • 我将此功能用于用户输入而不是阅读文档。所以我认为这无关紧要?
【解决方案3】:
  1. 它无缘无故地依赖于 '\n'=='0xa' 和 '\r' =='\0d'。如果您的意思是 \r\n,请使用它们。
  2. 它可能会非常慢,为您阅读的每个字符重新分配。
  3. sizeof(char) 保证为1,所以没有意义。
  4. 如果您分配了一块内存,然后 realloc 失败,您将返回 NULL 而不返回或释放 str,从而导致内存泄漏。
  5. 接口没有提供指示部分故障的方法,如 #4 所示。您所能做的就是返回一个字符串或不返回。给定一个巨大的输入字符串,你无法表明你已经阅读了部分而不是全部。

【讨论】:

  • 糟糕,我没看到我忘记释放str的值
【解决方案4】:

以下是前几个观察结果,其他答案包括更多:

  1. 它一次将缓冲区增加 1 个字节,因此进行了许多不必要的 realloc() 调用。
  2. 如果realloc() 失败,则之前的缓冲区丢失。
  3. 不是@987654321@,虽然它当然更便携。
  4. 硬编码换行和回车的 ASCII 值也不是很便携,请改用 '\n''\r'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-29
    • 1970-01-01
    • 2011-11-01
    • 2011-05-15
    • 1970-01-01
    • 2012-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多