【问题标题】:Manual allocation in a stringbuffer object在字符串缓冲区对象中手动分配
【发布时间】:2011-09-02 06:26:13
【问题描述】:

对于一个小型的嵌入式应用程序,我编写了一些函数+结构体,它们用作字符串缓冲区(类似于 C++ 中的 std::stringstream)。

虽然这样的代码工作正常,但有一些不那么小问题:

  • 我以前从未在 C 中编写过手动分配使用不断增长的内存的函数,因此恐怕还有一些怪癖需要解决
  • 似乎代码分配的内存比它实际需要的多得多,这是非常糟糕
  • 由于 valgrind 报告的警告,我已在代码中的某个位置从 malloc 切换到 calloc,这成功删除了警告,但我不完全确定我是否真的正确使用它李>

我的意思是它分配的比它真正需要的多的例子(使用 56k 文件):

==23668== HEAP SUMMARY:
==23668==     in use at exit: 0 bytes in 0 blocks
==23668==   total heap usage: 49,998 allocs, 49,998 frees, 1,249,875,362 bytes allocated

...看起来不太对劲...

有问题的代码在这里(太大,无法将其复制到 SO 上的 <code> 字段中):http://codepad.org/LQzphUzd

需要帮助,感谢您的任何建议!

【问题讨论】:

  • 问题到底是什么?

标签: c memory embedded malloc calloc


【解决方案1】:

您增加缓冲区的方式相当低效。对于每一小段字符串,您需要 realloc() 内存,这可能意味着分配了新内存并复制了“旧”内存的内容。这很慢并且会破坏你的堆。

最好以固定数量或固定百分比增长,即使新尺寸为旧尺寸的 1.5 或 2 倍。这也浪费了一些内存,但会使堆更可用,并且不会生成那么多副本。

这意味着您必须跟踪两个值:容量(分配的字节数)和长度(字符串的实际长度)。但这应该不会太难。

我将介绍一个函数“FstrBuf_Grow”来处理所有这些。您只需使用要添加的内存量调用它,FstrBuf_Grow 将通过在必要时重新分配来确保容量符合要求,至少在必要时进行。

...

void FstrBuf_Grow(FstringBuf *buf, size_t more)
{
    while (buf->length + more) > buf->capacity
        buf->capacity = 3 * buf->capacity / 2;
    buf->data = realloc(buf->data, buf->capacity + 1);
}            

capacity 乘以 1.5,直到 data 足够大。您可以根据需要选择不同的策略。

【讨论】:

  • 虽然您的解决方案不错,但函数 FstrBuf_Grow 将无限循环...
  • @hiobs:为什么要无限循环?当more 很大时,我可以想到一些场景,但我想知道您指的是哪一种。
  • @hiobs:我在 Delphi 中使用相同的代码,但它会进行溢出和范围检查,而 C 不会这样做。
  • 我认为这就是问题所在,它不能那么容易地从 delphi 转换为 C :-) 但正如 OP 中指出的那样,我也从未在 C 中做过类似的事情。如果可以选择,我总是选择 C++。以我的经验,C 不适合与字符串操作相关的东西......
【解决方案2】:

strncat(ptr->data, str, len);,移动到ptr->length = ((ptr->length) + len); 之前并使用strncpy(ptr->data+ptr->length...。而Destroy中的ptr = NULL;是没用的。

“库”的代码似乎是正确的,但请注意您正在不断地重新分配缓冲区。通常你应该尽量少增加缓冲区(例如每次你需要增加缓冲区时,你使用 max(2* 当前大小,4) 作为新大小),因为增加缓冲区是 O(n)。大内存分配可能是因为你第一次分配一个小缓冲区。然后你将它重新分配到一个更大的缓冲区中。然后你需要将它重新分配到一个更大的缓冲区中,这样堆就会增长。

【讨论】:

  • Be aware that you are continously reallocating the buffer. -- 我的方法有替代方案吗?
  • @hiobs:是的。长度成为缓冲区的长度(我们称之为lengthbuffer)。如果您愿意,您甚至可以保留字符串的长度(我们称之为 lengthstr),但这不是必需的(您始终可以使用 strlen)。 lengthstr + 1 总是
  • 我要补充一点,*2 和 4 是随机数。在 C# 2.0 中,StringBuilder 使用类似 max(lenghtstr + len + 1, max(lengthbuffer * 2, 16))
【解决方案3】:

看起来您在每次追加时都重新分配缓冲区。你不应该只在你想追加超过它可以容纳的东西时才增长它吗?

在重新分配时,您希望使用一种策略来增加缓冲区的大小,该策略可以在分配数量和分配的内存量之间进行最佳权衡。每次达到限制时将缓冲区的大小加倍可能并不适合嵌入式程序。

【讨论】:

    【解决方案4】:

    通常对于嵌入式应用程序,最好分配一个 1-3 倍于最大消息大小的循环 FIFO 缓冲区。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      相关资源
      最近更新 更多