C编程:循环中的malloc和free

6
我刚开始学习C语言,对于malloc()free()的性能问题知之甚少。我的问题是:如果我在一个循环内部调用malloc()然后再调用free(),这个循环会比在循环外部调用free()运行得更慢吗?假设这个循环需要执行20次。
实际上,我正在使用第一种方法来为缓冲区分配内存,从文件中读取可变长度的字符串,进行一些字符串操作,然后在每次迭代结束后清空缓冲区。如果我的方法产生了大量开销,那么我想请教有没有更好的方法来达到相同的结果。

5
如果你在循环内调用了malloc并且在循环外调用free,那么除了最后一次迭代使用的malloc之外,其余所有malloc分配的内存都会泄漏。但是,你可以使用realloc代替malloc,在循环外调用free即可避免这种情况。 - Cascabel
@Jefromi:我认为你应该把那个评论变成一个答案。 - Fred Larson
2
你的英语很好,事实上比大多数 SO 新手的英语都要好。 - Billy ONeal
@fred:嗯,我做了,但是Kenny的更好,因为它不会盲目地每次都重新分配内存。 - Cascabel
如果你想要对这样的事情进行基准测试,你需要使用超过20次迭代。200,000次是更好的选择,或者运行循环直到计时器到期,然后计算在5秒内运行了多少个循环。 - Zan Lynx
8个回答

16

肯定会更慢。(但请记住你需要平衡mallocfree的次数,否则可能会导致内存泄漏。)

如果长度变化,您可以使用realloc来扩展缓冲区大小。

void* v = malloc(1024);
size_t bufsize = 1024;

while(cond) {
   size_t reqbufsize = get_length();
   if (reqbufsize > bufsize) {
      bufsize = reqbufsize * 2;
      v = realloc(v, bufsize);
   }
   // you may shrink it also.

   do_something_with_buffer(v);
}

free(v);

2
+1 -- 但是你不觉得 bufsize = reqbufsize * 2; 有点过分吗? :P - Billy ONeal
5
那其实是一种相当普遍的做法。 - Dinah
3
缓冲区的大小通常会按比例增加,因为这样可以获得更好的平摊渐近运行时间。如果每次重新分配一个插槽并插入n个元素,则需要复制1 + 2 + ... + (n - 1) + n = O(n^2)次。然而,如果将缓冲区的大小加倍,则插入n个元素只需要O(1)次复制。您可以在哈希表维基百科文章中找到更严谨的解释:http://en.wikipedia.org/wiki/Hash_table#Resizing_by_copying_all_entries - Michael Koval
1
如果realloc失败,你就泄漏了它。你需要一个临时指针用于realloc,如果成功了,就将其复制到原始指针上,否则释放原始指针并中止并输出错误消息。 - Arthur Kalliokoski
2
@Arthur。是的。但如果realloc失败,您面临的问题比泄漏更大:)。 - kennytm
显示剩余3条评论

7

如果你在调用malloc内存分配函数时,不能在循环外部调用free释放内存:

char * buffer;
for (int i = 0; i < num_files; i++) {
    buffer = malloc(proper_length(i));
    // do some things with buffer
}
free(buffer);

你将会使用 num_files 次 malloc,但只释放了一次 - 你泄漏了除最后一个以外的所有内存!
有两种主要选择 - 如果你知道一个适用于所有情况的大小,则在循环之前 malloc(或者只使用数组),或者使用 realloc:
char * buffer = NULL;
for (int i = 0; i < num_files; i++) {
    buffer = realloc(proper_length(i));
    // do some things with buffer
}
free(buffer);

6

在前20次迭代中,您不必担心malloc/free的性能。

即使进行更多迭代(数个数量级),在分析代码并了解哪些内容较慢之前,也不应开始考虑优化。

最后,如果您要释放缓冲区,就没有必要先清除它。即使您将malloc/free移至循环外部(使用Justin建议的最大缓冲区),也不需要显式清除缓冲区。


3

如果您知道缓冲区的最大长度,或者可以设置一个合理的最大长度,则可以在每次迭代中使用相同的缓冲区。否则,您目前的做法应该是可行的。


2

这取决于您需要缓冲区的用途。

您是否真的需要在每次迭代后清除它,或者仅使用\0字符标记字符串的结尾即可?毕竟这就是各种str库调用的方式。

如果您确实需要清除它,可以使用bzero()。每次迭代都进行malloc和free是浪费资源的,因为您可以愉快地重复使用缓冲区。

如果您要并行化for循环(即有多个并发线程使用它),则会出现不同的问题。

简单的现实例子:使用桶来运输水。假设您需要多次使用该桶:拿起它,使用它,放下它,再次拿起它,使用它等等...是否有意义?您可以尽可能多地重复使用桶。 另一方面,如果需要其他人使用该桶,则需要组织访问该桶或需要更多的桶。

最后一个建议:现在不要担心性能问题。他们说早期优化是万恶之源,您很快就会明白为什么。

首先,了解问题:编写可丢弃的代码。试验。 其次,测试它。确保它可以满足您的需求。 第三,优化它。使循环运行一万次并测量所需时间。然后将malloc移动到外面,并再次测量(如果在UNIX下,请使用shell命令time)。 第四,重新编写代码,因为您的第一个实验很可能是一堆补丁、尝试-重试-不起作用的代码。

反复实践。

附:同时享受乐趣。这应该是有趣的,而不是令人沮丧的。


桶的例子非常形象生动。感谢您提供人性化的描述性答案。 - Y. E.

1

通常,任何可以移出循环的内容都应该被移出。为什么要重复相同的操作,当你只需要执行一次呢?

Justin Ethier是正确的,分配一个足够容纳最大字符串的缓冲区,并重复使用它。


0

更好地处理它。这里有一些伪代码:

#define BLOCK_SIZE 1024 // or about the bigger size of your strings.

char *buffer = (char *) malloc(BLOCK_SIZE) 

for(int i=0; i<20; i++)
{
   while (more bytes left to read)
   {
    read full string or BLOCK_SIZE bytes at max // most calls work this way
    proces bytes in buffer
   }
}

free(buffer);

0

这取决于malloc和free的实现方式。

回答你的问题最好的方法是建立一个基准测试...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接