我正在尝试检测macOS上C(和C ++)程序中的内存泄漏。在Linux和Windows中,我可以使用valgrind
轻松地做到这一点,但是不幸的是,它在macOS上不可用。
[由于我具有ObjC和iOS开发人员的背景经验,因此我考虑使用Instruments进行内存泄漏检查。乍一看,听起来很完美。
我写了这个[[very简单的泄漏程序:
#include <stdlib.h>
#include <stdio.h>
int* allocSomething() {
return malloc(sizeof(int));
}
int main(int argc, const char * argv[]) {
int* p = allocSomething();
*p = 5;
printf("*p = %d\n", *p);
p = NULL;
return 0;
}
我通过执行此任务的Clang静态分析器运行它,但是我也希望它也能被Instruments捕获,因为我正在寻找合适的Valgrind替代品。因此:
您可以看到,没有泄漏报告。在网上搜索后,我遇到了Can't detect C leaks in xcode 9 instruments,作者使用了sleep
,所以我认为Instruments实际上并没有以Valgrind的身份覆盖malloc
,而是使用了采样技术,并且没有对它进行采样这么短的通知,所以我将程序更改为:
int main(int argc, const char * argv[]) {
int* p = allocSomething();
p = NULL;
sleep(600000);
return 0;
}
现在,我得到:这完全没有意义,因为这是明显的内存泄漏。我会说它必须做一些优化,但随后我又明确禁用了它。另外,如果我再
malloc
个字节,它会检测到它。也许这是Instruments中的错误?所以,我想知道这是否是无法检测少量分配的文书问题?我必须指出,Valgrind可以很好地处理它,因此我感到惊讶。
您有任何建议吗?
首先,为什么对于短暂的进程它什么也没检测到:它是基于计时器的。它会经常停止该过程,并检查是否有泄漏。对于短暂的流程,检查永远不会在流程退出之前进行。
其次,为什么它会遗漏一些泄漏:它检查所有线程的堆栈,所有线程的寄存器以及寻找分配地址的全局变量。如果在任何这些地方都找到了分配的地址,则认为该分配没有泄漏。
在您的情况下,分配的地址可能仍在寄存器或堆栈存储器中。在“真实”程序中,堆栈和寄存器最终将被重用,并且这些陈旧的数据将被消除。
分配工具确实会跟踪所有分配和取消分配(假设该过程由Instruments启动)。您泄漏的分配位于分配列表中,并且仍列为“实时”(又称“已创建”和“永久”)。问题在于分配工具没有明确调用诸如泄漏之类的分配。
此外,系统库还进行了分配,这些分配旨在保留到进程退出之前,并且不会显式清除。因此,您的泄漏被隐藏在无关的信息中。您可以对分配列表进行过滤和排序以发现您的泄漏,但是需要做一些事情。
在更真实的程序中,Instruments非常擅长于发现泄漏。
显然,Clang支持LeakSanitizer,它可以作为AddressSanitizer的一部分,也可以独立使用。这可能需要比苹果作为Xcode和/或Catalina附带的版本更高的Clang版本。 (我在Mojave上使用Xcode 11.3.1进行了测试。它不支持-fsanitize=leak
选项,也不支持ASAN_OPTIONS=detect_leaks=1
。)假设您可以使用它,那么它的行为可能会更像您期望的那样。