C字符串中'\ 0'之后的内存会发生什么变化?
令人惊讶的简单/愚蠢/基本问题,但我不知道:假设我想返回我的函数的用户一个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 = ' ';
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] = ' ';
outstr[0] = original[0];
outlen = 1;
// Construct output
prev = *original++;
while (*original) {
if (*original != prev) {
outstr[outlen++] = *original;
prev = *original;
}
++original;
}
return outstr;
}
如果我在分配的内存中间粘贴' 0',确实如此
(a。)free()仍然正常工作,并且
是。
(b。)' 0'后面的空格变得无关紧要了吗? 一旦添加' 0',内存是否会返回,还是坐在那里占用空间直到释放()被调用?
依靠。 通常,当你分配大量的堆空间时,系统首先分配虚拟地址空间 - 当你写入页面时,一些实际的物理内存被分配给它(当你的操作系统具有虚拟内存时,它可能会在以后被换出到磁盘支持)。 有趣的是,虚拟地址空间的浪费分配与实际的物理/交换内存之间的这种区别允许稀疏阵列在这些OS上具有合理的存储效率。
现在,这个虚拟寻址和分页的粒度在内存页面大小 - 可能是4k,8k,16k ...? 大多数操作系统都有一个可以调用的函数来查找页面大小。 因此,如果您正在进行大量的小分配,那么将页面大小舍入为浪费,并且如果您的地址空间相对于您真正需要使用的内存量有限,则需要根据上述方式使用虚拟寻址不会缩放(例如,32位寻址的4GB RAM)。 另一方面,如果你有一个64位的进程运行32GB的内存,并且这样做的字符串分配相对较少,那么你就有大量的虚拟地址空间可供玩耍,并且四舍五入为页面大小赢得'吨金额很大。
但是 - 请注意写入整个缓冲区之间的区别,然后在某个更早的时间点终止它(在这种情况下,一次写入的内存将具有后备内存并可能以交换结束),而不是拥有一个只能写入大缓冲区的大缓冲区到第一个位然后终止(在这种情况下,后备存储器仅分配给用完的页面大小的空间)。
另外值得指出的是,在许多操作系统上堆内存可能不会返回到操作系统,直到进程终止:相反,malloc / free库在需要增长堆时通知操作系统(例如,在UNIX上使用sbrk()
或Windows上的VirtualAlloc()
)。 从这个意义上说, free()
内存对于你的进程可以重用是免费的,但对于其他进程的使用则不是免费的。 一些操作系统会优化这一点 - 例如,对于非常大的分配,使用独立且可独立释放的内存区域。
为了节省一些前期编程时间,在调用malloc之前计算必要的空间,是否通常是不好的编程风格?
同样,这取决于你正在处理多少这样的分配。 如果相对于你的虚拟地址空间/ RAM有很多相关信息 - 你想明确地让内存库知道并不是所有最初请求的内存实际上都需要使用realloc()
,或者你甚至可以使用strdup()
来分配一个新的根据实际需要(然后free()
原始)来更紧密地阻止 - 取决于您的malloc / free库实现可能会更好或更糟,但很少有应用程序会受到任何差异的显着影响。
有时候你的代码可能在一个库中,你无法猜测调用应用程序将要管理多少个字符串实例 - 在这种情况下,最好提供速度较慢的行为,这样做永远不会太糟糕......所以我们倾向于将内存块缩小到适合字符串数据(一组额外的操作,因此不会影响大O效率),而不是浪费原始字符串缓冲区的未知比例(在病态情况下 - 在任意大的分配后使用零个或一个字符)。 作为性能优化,如果未使用的空间大于已使用的空间,则可能只打扰返回的内存 - 调整尝试,或使调用者可配置。
您对另一个答案发表评论:
因此,判断realloc是否需要更长时间或预处理大小的决定呢?
如果表现是你的首要任务,那么是的 - 你会想要个人资料。 如果你不是CPU绑定的话,那么通常采用“预处理”命令并做一个合适的大小分配 - 只有更少的碎片和混乱。 反驳,如果你必须为某些功能编写一个特殊的预处理模式 - 这是一个额外的“表面”,用于维护错误和代码。 (当从snprintf()
实现你自己的asprintf()
时,这种折衷决定通常是需要的,但至少你可以相信snprintf()
按照记录行事,并且不必个人维护它)。
一旦添加' 0',内存是否会返回,还是坐在那里占用空间直到释放()被调用?