Calling dlopen then dlclose causes an increase in the amount of memory used by the program. If I create a loop that calls dlopen / dlclose repeatedly on the same dynamic library, memory usage increases continuously. Is this a bug, or am I using dlopen / dlclose incorrectly?
I can reproduce this by modifying the sample code in the Apple Developer docs Creating Dynamic Libraries. If I modify Runtime.c, changing the line void *lib_handle = dlopen(lib_name, RTLD_NOW); to add the infinite loop, as below:
void *lib_handle = dlopen(lib_name, RTLD_NOW);
for (int ii = 0; ; ++ii) {
printf("loop %i\n", ii);
int close_err = dlclose(lib_handle);
printf("close error: %i\n", close_err);
printf("dlopen(%s, RTLD_NOW)\n", lib_name);
lib_handle = dlopen(lib_name, RTLD_NOW);
}
then opening and closing the dynamic library will succeed, but memory usage (as reported by top) will rapidly increase.
I'm running on x86_64 macOS 13.6.6. Full code for the modified Runtime.c is attached, the rest of the code is available in the Apple Developer docs.
Any suggestions?
Many thanks, Chris
I tried this here in my office and didn’t see any leaks or unbounded memory growth. Here’s what I did:
-
Using Xcode 26.1 on macOS 15.7.1 [1], I created a new project from the macOS > Command Line Tool template.
-
I then added a dynamic library target to that.
-
I populated them with the code shown at the end of this post.
-
I build both targets using Xcode.
-
I then ran the tool from Terminal:
% ./Test806035 will run will open and close, iteration: 0 did close and close … will open and close, iteration: 999 did close and close did run, press return to loop, pid: 47371 -
In other Terminal window, I rans
leaksagainst the process. It showed no leaks. -
I ran
heapagainst the process:% heap Test806035 … COUNT BYTES AVG CLASS_NAME TYPE BINARY ===== ===== === ========== ==== ====== 134 4288 32.0 Class.data (class_rw_t) C libobjc.A.dylib 27 11360 420.7 non-object 5 512 102.4 xpc_string_t (Storage) C libxpc.dylib 5 256 51.2 xpc_dictionary_t (Storage) C libxpc.dylib 5 240 48.0 xpc_string_t ObjC libxpc.dylib 4 128 32.0 Class.methodCache._buckets (bucket_t) C libobjc.A.dylib 2 192 96.0 xpc_dictionary_t ObjC libxpc.dylib 1 128 128.0 dispatch_queue_t (serial) ObjC libdispatch.dylib 1 64 64.0 xpc_array_t (Storage) C libxpc.dylib 1 64 64.0 xpc_pipe_t ObjC libxpc.dylib 1 48 48.0 Class.data.extended (class_rw_ext_t) C libobjc.A.dylib 1 48 48.0 xpc_array_t ObjC libxpc.dylib -
Back in the tool window, I repeatedly pressed return and waited for it to complete.
-
I repeated step 7.
-
I compared the
heapoutput from step 7 and 9. It was identical.
I recommend that you repeat this test to confirm my result. If you see anything different, let me know. Otherwise, you can start comparing my code to your code to see why your code shows unbounded memory growth when mine doesn’t.
If I had to guess, I’d say that this is most likely caused by the library you’re loading. Does it have any library initialiser functions? A common cause of those is C++ global constructors? If so, is it possible that those are leaking? Or that they’re not be destructed correctly?
You can use the -Wglobal-constructor option to check for C++ global constructors.
https://clang.llvm.org/docs/DiagnosticsReference.html#wglobal-constructors
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
[1] You need to update (-: Seriously though, the chances of this being different between 15.6.1 and 15.7.1 are very low.
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char **argv) {
#pragma unused(argc)
#pragma unused(argv)
printf("will run\n");
while (true) {
for (int ii = 0; ii < 1000; ++ii) {
printf("will open and close, iteration: %d\n", ii);
void * lib_handle = dlopen("./libLeakTest.dylib", RTLD_NOW);
if (lib_handle == NULL) {
printf("did not open, error: %s\n", dlerror());
abort();
}
int err = dlclose(lib_handle);
if (err != 0) {
printf("close failed, error: %s\n", strerror(err));
abort();
}
printf("did close and close\n");
}
printf("did run, press return to re-run, pid: %d\n", (int) getpid());
char junk[16];
fgets(junk, sizeof(junk), stdin);
}
return 0;
}
#include <stdio.h>
extern void LTDummyRun(void);
extern void LTDummyRun(void) {
printf("LTDummyRun");
}