这个问题看起来很简单/愚蠢/基础,但我不知道:假设我想向我的函数用户返回一个C字符串,但我不知道函数开始时它的长度。我只能在一开始就给定一个上限,并且根据处理情况,大小可能会缩小。
问题是,是否有问题在处理期间分配足够的堆空间(上限),然后在字符串远未占满分配的内存的情况下终止呢?也就是说,如果我在分配的内存中间插入 '\0',那么 (a.) free()
仍然可以正常工作吗? (b.) '\0' 后面的空间是否变得无关紧要?加了 '\0' 后,内存只是被释放还是一直占用着空间直到调用 free()
?为了节省一些前期计算 malloc 所需空间的时间,留下这个悬挂的空间通常是不良的编程风格吗?
为了更好地理解,假设我想删除连续的重复项,就像这样:
输入 "Hello oOOOo !!" --> 输出 "Helo oOo !"
...以下代码显示了如何预先计算我的操作结果的大小,以有效地进行两次处理以正确获取堆大小。
char* RemoveChains(const char* str)
{
if (str == NULL) {
return NULL;
}
if (strlen(str) == 0) {
char* outstr = (char*)malloc(1);
*outstr = '\0';
return outstr;
}
const char* original = str; // for reuse
char prev = *str++; // [prev][str][str+1]...
unsigned int outlen = 1; // first char auto-counted
// Determine length necessary by mimicking processing
while (*str) {
if (*str != prev) { // new char encountered
++outlen;
prev = *str; // restart chain
}
++str; // step pointer along input
}
// Declare new string to be perfect size
char* outstr = (char*)malloc(outlen + 1);
outstr[outlen] = '\0';
outstr[0] = original[0];
outlen = 1;
// Construct output
prev = *original++;
while (*original) {
if (*original != prev) {
outstr[outlen++] = *original;
prev = *original;
}
++original;
}
return outstr;
}
free()
来释放从函数返回的对象是不好的风格,因为调用者可能链接到不同的 C 库,并且这也会阻止您在未来使用不同的分配器。您应该提供一个小的包装函数来释放从库中返回的字符串。 - Simon Richterfree()
,但这现在是一个实现细节。如果你更改RemoveChains()
以使用不同的分配函数,你也可以适应包装器,并且现有程序将继续运行。 - Simon Richtermalloc
分配一个足够大但不要太大(例如256字节)的缓冲区来管理未知大小。然后,你可以向该缓冲区写入数据,并跟踪剩余空间的大小。如果空间不足,则使用两倍大小(例如512)进行realloc
,并继续写入数据。反复执行此操作。重新分配所花费的总时间最坏情况下为O(n)
,其中n
是最终长度,在许多情况下,它将是O(log n)
,因为如果缓冲区后面有足够的未分配空间,realloc
不必复制数据。你可以在结束时使用正确的大小进行realloc
。 - Nicu Stiurcafree()
进行解除分配,那么这与告诉人们要使用FreeStringFromRemoveChains()
一样是惯例--也就是说,你没有赢得任何东西,但你失去了以后引入更高效的分配器的灵活性(因为分配器成为API的一部分),并且在库链接到不同的C库的调用者时引入了一个微妙的错误。 - Simon Richter