Reputation: 3099
As an example, this code segfaults (see comment in main.cpp).
hello.h
struct A { virtual ~A() {} };
hello.cpp
#include "hello.h"
extern "C" {
void hello(A*& a) {
a = new A;
}
}
main.cpp:
#include <cassert>
#include <dlfcn.h>
#include "hello.h"
int main() {
void* handle = dlopen("./hello.so", RTLD_LAZY);
assert(handle);
typedef void (*hello_t)(A*& a);
hello_t hello = (hello_t) dlsym(handle, "hello");
assert(hello);
A* a = nullptr;
hello(a);
dlclose(handle);
delete a; // -> segfault
}
compile
g++ -Wall -std=c++11 -g main.cpp -ldl -o main
g++ -Wall -std=c++11 -g -shared -fpic hello.cpp -o hello.so
The reason: A
contains a virtual table. When a
is being allocated, the virtual table contains pointers to the function segment of hello.so
. After a call to dlclose()
, this function segment may get invalid, causing the pointers to get invalid, and thus, the delete a
calls functions which are out of memory.
So far so clear. My questions are, all assuming that you dlclose()
your library and then continue using the objects:
std::string
is guaranteed to be implemented without a virtual destructor, who knows if it holds an internal object with one?Upvotes: 4
Views: 2193
Reputation: 213358
There is no problem with dynamically linked libraries. They are not dangerous.
However, you are demonstrating the problem of unloading dynamically loaded libraries. You can't unload a library and then continue to use references to data in that library: data pointers, function pointers, virtual functions, et cetera. It is just like using a pointer after you delete
it: the only answer is to avoid calling delete
until you are done with the object.
So, using dynamically loaded libraries with dlopen()
and dlclose()
are as dangerous as new
and delete
... there is nothing to prevent you from doing the wrong thing, you have to know how to use these features correctly.
In typical usage (without dynamic loading), a dynamically linked library will remain resident for the entire duration of the program's execution. So if std::string
is in a dynamic library, its functions will always be available. Just like a static library.
Upvotes: 10
Reputation: 129374
Your analysis is incorrect. The problem is not that the after dlclose(handle)
the code for your destructor doesn't exist any longer, which means that your code crashes. Keep the library open, and the destructor can be called.
However, you do in some cases also get a different heap per shared library, so it's a good idea to keep the construction and destruction for dynamically allocated objects within the shared library. You could achieve this by a second function bye()
that does delete
of your object.
Typically the problem of "adding stuff dynamically" (your example with a std::string
is not great, as the implementation should only ever allocate char
elements, but std::vector
would potentially allocate things when copying elements, for example) is handled by encapsulating it within the object, that way the allocations are done by the code that is in the shared library.
(There are details in your analysis that are wrong too: the vtable itself is not stored on the heap, but the vptr that points to it has to be stored within the object, and in this case, that is on the heap)
Upvotes: 5
Reputation: 34563
The fact that the destructor is virtual isn't really relevant here, because there's no inheritance involved. The problem is simply that you're calling a function (the destructor) that's implemented in a library after you've closed that library. A direct call to a non-virtual member function would cause the same problem, because the function's code is in the library and the library has been unloaded.
Just keep the library open until you're done using the objects it implements.
Upvotes: 5