我正在编写一个程序来标记文本并根据标记化对其进行转换。标记由结构表示:
struct token {
enum token_type type;
size_t length; /* as returned by strlen(token.text); */
char text[]; /* 0-terminated */
};
tokenizer提供了一个迭代器接口,在调用时分配并生成下一个标记。然后,此函数的调用者处理令牌,将其传递给多个函数(其中一些函数可能会自己存储令牌),并可能将其存储在某处。
在某个时间点,令牌处理已完成,所有令牌都可以被释放。
我应该如何继续追踪令牌的分配?
我有三个想法:
每个标记都包含指向前一个标记的指针;最后,我可以简单地遍历链表以释放所有令牌。一旦我在多个地方创建令牌,这就变得复杂了。
每个令牌都包含一个引用计数器。这很复杂,因为我需要密切注意我保留对令牌的引用。
每个函数都复制令牌,而不是保留对它们的引用。如果函数传递一个标记作为参数,它就不能保留它们。这可能会导致大量不必要的内存分配。
从更有经验的程序员那里获得一些信息会很不错。
答案 0 :(得分:2)
由于您说在某个时间点需要同时释放所有令牌,因此您可以使用内存池。编写一个令牌分配器,它将malloc
令牌并将分配的指针存储在某个数组链表中,或者您想要解决它。完成所有处理后,调用一个可以同时释放所有令牌的函数。
像这样的东西(未经过测试,但你应该明白这一点):
struct token {
struct token *token_next;
enum token_type type;
size_t length;
char *text;
};
struct token_pool {
struct token *token_list;
};
struct token *
token_alloc(struct token_pool *pool, size_t len, enum token_type type)
{
struct token *t;
if ((t = malloc(sizeof(*t) + len)) == NULL)
return NULL;
t->length = len;
t->text = (char *)(t + 1);
t->type = type;
t->token_next = pool->token_list;
pool->token_list = t;
return t;
}
void
token_free_pool(struct token_pool *pool)
{
struct token *t;
while ((t = pool->token_list) != NULL) {
pool->token_list = t->token_next;
free(t);
}
}
这里的高级水平是将内存分配给更大的平板,这样你就不需要多次调用malloc了,但是因为你要使用动态大小,所以它可能只是矫枉过正。
apache使用它们的apr_pool API。在apache中分配的许多内容都有非常特定的生命周期(每个服务器或每个调用),因此避免泄漏并优化分配和释放非常容易和有效。
答案 1 :(得分:1)
这取决于令牌的寿命以及你有多少额外的记忆。正在做与令牌明显不同的事情(例如创建一个新字符串,或者将其存储以供日后使用)应该可以创建一个副本。
对于其余的我会使用引用计数器 - 但是创建一个函数(或宏)来创建引用并更新计数器(以及释放引用并递减计数器的对应物)。
然后更容易找到错误的引用,因为任何时候你必须使用该函数 - 如果你不这样做它是不安全的。您甚至可以在其中放置调试逻辑,以检查何时释放引用,并且如果引用是安全的,则可以通过某种方式跟踪引用。在调试模式下,您可以将参考生成实现为副本,以允许您添加跟踪信息。
答案 2 :(得分:0)
也许简单的主列表就足够了。
分配令牌时,将令牌指针添加到主列表。完成处理后,请浏览列表并取消分配所有内容。
修改强>
这可以通过拥有一个Tokenizer
对象(一个结构)来实现,该对象具有一系列功能,可以自动管理令牌的生命周期。
typedef struct {
/* list for tokens */
} Tokenizer;
void Tokenizer_Init(Tokenizer * this); // Must be called first
void Tokenizer_DeInit(Tokenizer * this); // Must be called last
void Tokenizer_DoStuff(Tokenizer * this);