cxcodememory-leaksvalgrindxcode-instruments

Detect C/C++ command line memory leaks using Instruments


I'm trying to detect memory leaks in C (and C++) programs on macOS. In Linux and Windows, I could do so easily using valgrind, but unfortunately, it's not available on macOS.

As I have background experience with ObjC and iOS dev, I thought to use Instruments to do the memory leak check. At first glance, it sounded perfect for the job.

I wrote this very simple leaked program:

#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;
}

I ran it through Clang Static Analyzer which did the job, but I wanted it to be caught in Instruments as well because I'm looking for a proper Valgrind replacement. Thus:

However, after using Instruments: Instruments does not detect leaks

and as you can see, no leak reported. After searching online, I came across Can't detect C leaks in xcode 9 instruments, in which the author used sleep, so I thought maybe Instruments doesn't actually override malloc as Valgrind, but use a sampling technique, and it doesn't sample it in such short notice, so I changed the program into:

int main(int argc, const char * argv[]) {
  int* p = allocSomething();
  p = NULL;
  sleep(600000);
  return 0;
}

Now, I get: no leak at all

Which totally doesn't make sense, as it's an obvious memory leak. I'd say that it has to do something with optimization, but then again I disabled it explicitly. In addition, if I malloc more bytes, it does detect it. Or maybe it's a bug in Instruments?

So I wonder if it's an issue with Instruments that can't detect small allocations? I must note that Valgrind can deal with it very well so I'm surprised.

Do you got any suggestions?


Solution

  • The Leaks instrument (one of the instruments in the Leaks template) works differently than you're expecting.

    First, why it doesn't detect anything for short-lived processes: it is timer-based. It stops the process every so often and checks it for leaks. For a short-lived process, the check never happens before the process exits.

    Second, why it can miss some leaks: it examines the stacks of all threads, the registers for all threads, and global variables looking for the addresses of allocations. If an allocation's address is found in any of those places, that allocation is not considered to have leaked.

    In your case, the address of the allocation is probably still in a register or stack memory. In a "real" program, the stack and registers would eventually be reused and such stale data would be eliminated.

    The Allocations instrument does track all allocations and deallocations (assuming the process was launched by Instruments). Your leaked allocation is in the allocation list and still listed as "live" (a.k.a. Created & Persistent). The problem is that the Allocations instrument does not explicitly call out such allocations as leaks.

    In addition, the system libraries have also made allocations that are intended to survive until process exit and are not cleaned up explicitly. So, your leak is a bit buried in irrelevant information. You can filter and sort the allocation list to uncover your leak, but it takes some doing.

    In the context of a more real program, Instruments is quite good at finding leaks.

    Apparently, Clang supports a LeakSanitizer, either as part of the AddressSanitizer or standalone. That may require a version of Clang that's newer than what Apple ships as part of Xcode and/or Catalina. (I tested with Xcode 11.3.1 on Mojave. It doesn't support the -fsanitize=leak option and ASAN_OPTIONS=detect_leaks=1 wasn't supported either.) Assuming you can get that to work, that might behave more like what you're expecting.