2

Dealing with strings in C definitely makes one wish for a simple class-based language, but I'm trying to instead build a convenient string library. My idea is to use immutable strings, with a struct called Rstring (for "robust string") that has an internal const char* s and int length, such that operations like rstring_concat and rstring_substring return new Rstring objects with their own newly malloc'd character pointers.

Writing up an initial draft of the library, I am pleased with the simplicity and cleanliness of using my library instead of char *. However, I realized that returning a newly allocated pointer is somewhat of a PITA without a destructor. Each time some operation is done, say via concatenation or substrings, you have some newly allocated memory, and then whichever strings you had before are now hanging around, and probably are useless, so they need to be free'd, and C has no destructors, so the user is stuck having to manually go around freeing everything.

Therefore my question is, are there any clever ways to avoid having to make a whole bunch of manual calls to free? Possibly, for example, having internal start and end indices in order to have strings which act like small strings, but really contain quite a bit more? I don't know if there's any generally accepted method of doing this, or if people are simply stuck with tedious memory management in C.

Perhaps best, is there any widely-used library for convenient string manipulation in C?

4

5 回答 5

3

如果您需要一个更好的 CI 字符串库,建议使用The Better String Library


C 没有任何方法可以简化内存管理。您使用 malloc 分配的任何内存都必须被释放。如果您在一个函数中处理大量字符串,您可以使用一个特殊的registry地方来注册字符串。然后注册表可以销毁所有注册到它的字符串。

例如(只有接口,没有实现)

void rstring_reg_init(rstring_reg*);
void rstring_reg_destroy(rstring_reg*);
rstring rstring_reg_create(rstring_reg*, const char*);
void rstring_reg_register(rstring_reg*, rstring);
void rstring_reg_detach(rstring_reg*, rstring);

如果您的字符串是可变的,您甚至可以使用注册表创建字符串(我宁愿将其称为池)。如果字符串要记住它们的池,您甚至可以让它们在创建时注册自己。这可能会导致相当“漂亮的代码”,例如:

rstring f() {
    rstring_reg reg;
    rstring_reg_init(&reg);
    rstring a = rstring_reg_create(reg, "foo");
    rstring b = rstring_reg_create(reg, "bar");
    rstring ab = rstring_concat(a, b);
    rstring s = rstring_substr(ab, 1, 4);
    rstring_detach(s);
    rstring_reg_destroy(&reg);
    return s;
}

这段代码的作用是:

  • 创建注册表
  • 创建ab字符串都知道注册表
  • 创建一个新ab字符串。它会自动添加到注册表中。
  • 创建一个新s字符串。它也被添加到注册表中。
  • 从注册表中分离s,因为我们要返回它。
  • 销毁注册表。这会自动销毁a,bab
  • Return s- f 的调用者现在负责管理它的内存

最后,我宁愿推荐使用 C++ 而不是使用这种野兽。

你真正想要的是RAII,这只能使用 C++ 或专有的 GCC 扩展。

于 2013-02-03T01:54:36.607 回答
0

您可能想看看Glib,它有许多有用的数据结构和服务,其中包括类型Gstring。有一个printf将其结果直接放入 Gstring 中。非常便利。

从本质上讲,您的问题更多是关于内存分配而不是字符串本身。对于字符串或其他任何内容的内存分配,您从 C 中获得的帮助很少。有几种通用方法:

  1. 手动: 为每个对象调用 malloc() 和 free()。
  2. 保守的垃圾收集: Boehm 收集器非常成熟。虽然它有一些问题,但在这一点上它们很好理解。必须避免某些古怪的编码方法和激进的优化。
  3. 引用计数垃圾收集: 这要求您在创建和销毁指向它的每个指针时调用referencedereference增加/减少字符串中的计数器的例程。当递减使计数为零时,字符串被释放。这与 malloc() 和 free() 一样乏味且容易出错,但它允许您处理字符串的生命周期可能以几个单独的代码块中的任何一个结束的复杂情况。
  4. 池分配。 在此方案中,您可以创建任意数量的分配池。创建字符串时,您指定应该分配它的池。池立即全部释放。
  5. 堆栈分配。 字符串是从具有标记和释放操作的单个内存池中分配的。调用 Release 将所有字符串释放回上次调用 Mark 的位置。

以上所有的组合是常见的。例如,GNU Obstacks结合了 4 和 5。

于 2013-02-03T03:37:17.603 回答
0

至少据我了解,不可变字符串的全部意义在于能够通过共享存储来避免复制。(如果这很重要,您也可以避免一些锁定问题。)但是在这种情况下,您真的不能允许客户端释放字符串,除非您强制它们保持引用计数,这真的很痛苦。

但是,在许多应用程序中,您可以执行 Apache Runtime (APR) 所做的工作,并使用内存池。在内存池中创建的对象不会单独释放;整个池被释放。这需要一些生命周期分析,并且可能会导致难以理解的错误,因为编译器不会为您做簿记,但它通常比引用计数少,另一个好处是分配和释放都非常快。这可能会抵消另一个缺点,即释放存储有点不精确。

如果您有某种基于请求的控制流,典型的服务器(但也适用于某些类型的 UI 应用程序),那么内存池架构效果最好。在基于请求的结构中,每个请求都会初始化一个内存池,并在请求处理完成时释放它。如果请求对服务器的状态进行了某种永久性更改,您可能必须将一些数据从临时池移动到(更多)永久池,但这种更改相对较少。对于可以优雅地划分为阶段的请求,其中一些是临时需要内存的,可以嵌套内存池;在这里,您可能需要为封闭的内存池显式标记某些字符串,或者在删除它们的内存池之前移动它们。

由于同时删除了内存池中的所有对象,因此可以通过将终结器附加到池本身来实现终结器。池可以保留一个简单的终结器链表,这些终结器在实际释放池之前按顺序(以相反的创建顺序)执行。这允许终结器引用同一池中的任意其他对象,前提是它们不会使对象的状态无效;这个限制并不太严格,因为总的来说,终结器是关于管理非内存资源(如文件描述符)的。

APR 没有不可变的字符串(或者,至少在我上次查看它时没有),因此 Apache 最终会进行大量不必要的字符串复制。这是一种设计选择;我在这里不会以一种或另一种方式投票。

于 2013-02-03T02:55:57.797 回答
0

您真正需要的是C 的垃圾收集器

LISP 和 Java 程序员认为垃圾收集是理所当然的。借助 Boehm-Demers-Weiser 库,您也可以轻松地在 C 和 C++ 项目中使用它。

于 2013-02-03T02:49:43.370 回答
0

声明不可变字符串结构的“稍微聪明”的方式类似于:

struct Rstring {
    size_t length;
    char s[0];
};

这是实际中的零长度数组破解。您可以Rstring按如下方式分配对象:

struct Rstring* alloc_rstring(const char* text) {
    size_t len = strlen(text);
    struct Rstring* ret = malloc(sizeof(Rstring) + len + 1));
    ret->length = len;
    memcpy(ret->s, text, len + 1);
    return ret;
}

并使用简单的 释放此类 Rstring 对象free(),因为字符串数据位于相同的分配中。

于 2013-02-03T02:40:56.470 回答