free 中是否有额外的开销来检查空指针?

Is there any extra overhead in free that checks for a null pointer?

我不是在问我是否需要在调用 free 之前检查指针是否为 NULL,我知道我不需要。相反,我问的是 free 的(典型)实现是否牺牲了在释放之前检查 NULL 指针的时间,或者进程的性质是否实际上不会调用任何额外的开销。例如,知道 free 做什么的人可能会想象它的实现可能是这样的:

void free(void* ptr){
    if(ptr != NULL){
        <proceed with deallocation>
    }
}

但这与 C 语言不为我不用的东西付费的理念相矛盾:如果我知道我不会将 NULL 指针传递给 free,那么就不会有性能缺陷因此。因此,我很确定 free 而不是 通常会调用这样的成本,在那种情况下我想知道 how/why。我试着寻找这个问题的答案,但我看到了一大堆问题,询问删除 NULL 指针是否安全。

编辑:我已经对答案和评论感到满意,但为了将来参考,我想弄清楚我在问什么。我 而不是 从程序员的角度询问在我调用 free 之后程序和我的 NULL 指针的状态。我对堆管理器的观点更感兴趣。举个例子说明我的意思,假设我在一家面包店,我的工作是把 table 里的东西拿走,然后全部扔进垃圾桶。可能是 5 个核仁巧克力饼、一打饼干或其他任何东西,是的,我知道这不是最好的类比,但请耐心等待。我想知道是否存在堆管理器,例如,如果 table 上有 "nothing",我将 "nothing" 倾倒到垃圾桶中是否会有任何不同,这将是一个隐式 处理 "nothing." 一个 显式 处理 "nothing" 将是我检查 table 以查看如果在我尝试倾倒它之前上面有任何东西。我想知道堆管理器是否通常可以隐式地处理这种情况,即,通过做他们通常会做的同样的事情,顺便也处理一个 NULL 指针,或者显式地,通过显式检查是否指针是 NULL。正如 awksp 的评论所揭示的那样,由于在 NULL 指针的情况下要求不执行任何字面操作,因此必须确实存在显式检查,因为将 NULL 指针与其他指针一样对待不是空操作,因此我们必须事先明确检查 NULL

实际上 free 所做的只是将分配的内存块(如果有的话)返回给您的操作系统,无论它是使用 malloc 还是 realloc 或其他方式分配的,应该有一个指向块开始的指针和保存在某处的块的长度。它显然(在大多数平台上)为每个分配的字节分配 8 个字节的开销,这是一个很大的开销(就内存使用而言),但这是操作系统在现实中所做的。

所以当 null pointer 传递给 free 函数时,根据上面所说的谈论 ANSII C 将什么都不做,因为它指向没有内存块,因此只要它在后台什么都不做,就会产生时间开销。

mallocfree 实现始终是开发 operating systems 的最大问题之一。所以我会建议自己实施一次,然后尝试尽可能减少开销。

首先 - 在空指针上调用 free() 并没有真正做任何事情。不用查,零的情况下你就cover了。

其次 - 在任何现代系统上,在使用指针之前检查指针都会有很小的开销,甚至可能是一个时钟周期,这实际上可以忽略不计,所以不要犹豫 - 在需要时检查。不是 free() 的情况,但有许多情况你想检查。

指针在使用前必须到达 CPU 寄存器,因此对 null 的额外检查将非常有效。跟从ram取数据或者thread context switching比起来简直是小巫见大巫

这就是为什么在 free() 中检查 NULL 不仅仅是一个好主意:

当您使用 malloc() 时,您不必记住您请求了多少内存:堆管理器会为您完成。因此,当您释放一个块时,它必须将释放的块(具有正确的大小)添加到它用来保存此信息的任何数据结构中。

那么,它怎么知道大小呢?它可以在其他包含所有已分配内存列表的结构中找到指针,但如果没有某种 Content-addressable-memory 硬件,这可能会很慢。因此,大多数堆管理器使用一种技巧来存储有关您在内存中分配的块的元数据紧接在他们给您的指针之前。 (这不可能 在块的末尾,因为记住我们不知道实际的块大小...)

如果堆管理器没有明确检查 NULL,它将开始尝试访问负地址(例如 NULL-16)的内存,这可能会导致访问冲突和各种其他不良影响。在开始之前有一个微不足道的 (if x=NULL) 更好。

它会导致性能下降吗?有可能,但与典型堆管理器中所需的所有块合并、列表搜索等相比,它完全微不足道。