我目前正在学习C,而且我常常不太确定如何最好地分配我的记忆力。我知道我们可以像这样进行自动或动态内存分配:
size_t foo = 128;
char str1[64]; // Automatic
char str2[foo] // Automatic (variable-length, C99)
char *str3 = malloc(64); // Dynamic
char *str4 = malloc(foo); // Dynamic
我也明白,通过自动分配,这些数组的范围有限,内存通常在堆栈上分配,而动态分配则使用堆。
让我们坚持使用字符串并说一个函数应该返回一个它以某种方式创建的字符串。似乎有两种常见的做法,都可以在标准库函数中看到:
malloc()
创建一个字符串并返回指向它的指针第一个优点是内存管理更安全,因为我们不需要记住free()
。另一方面,我们需要提交两个额外的参数,并且需要从外部确定缓冲区应具有的大小:
char str1[128]; // Let's hope this is large enough!
fill_my_buffer(str1, 128); // Might need more than 128, who knows
char *str2 = return_a_ptr(); // Just give me a pointer
size_t len = strlen(str2); // Ah, so that's what we got!
我注意到我开始在所有地方混合两种方法,这似乎不对。 是否有使用时的经验法则在C中选择自动或动态内存分配时,应该考虑两者之间的重要优缺点是什么?
答案 0 :(得分:4)
一个经验法则是你应该避免"大"堆栈上的分配。一页(4096字节)或两页很好,但任何更大的页应该是堆分配的。
过多地更改堆栈指针会导致缓存性能下降,而缓存则为王。
此外,您可能会溢出堆栈并且错过保护页面,尽管这主要是线程必须具有有限大小的堆栈的问题,或者在其他受限制的系统上可以&#39 ; t具有自动主线程堆栈增长。
除此之外,主要问题是语义:这个(小)对象"属于"到这个堆栈框架,或者它是否需要在不同的时间段内生效(更长或更短)。
调用free
的需要根本不应该是一个问题,所有函数的排序总是必须成对调用(这是C ++尝试的主要问题)地址,以及GNU的__attribute__((cleanup))
扩展名,你应该养成始终知道这样做的纪律。
值得注意的是,采用指针的每个函数都应该具有"谁负责这种所有权的概念"?常见的所有权模型包括:借用(由一些更高的堆栈框架拥有),独特拥有和共享(通常是refcounted)所有权,尽管可能有许多微妙的变体(尽管不是所有的都在同一个程序中)。
答案 1 :(得分:2)
它基本上是一个判断调用 - 调用代码可以知道缓冲区可能有多大吗?
调用者提供的缓冲区的复杂性是"当提供的空间不够大时该怎么办"。调用者提供的缓冲区的复杂性是确保它被正确释放并决定在内存分配失败时该怎么做。