Reputation: 621
When working with windows dll's, it is established that we should limit the memory allocation/deallocation within the dll boundary since the dll could be using its own heap. So we have export allocators and free functions to from dll.
IsomeInterface* getObject();
void freeObject(IsomeInterface *object);
This way the object creation and deletion will reside within the dll.
Does this problem also exist on shared libraries on linux? When dealing with shared libs (.so) do we also need to take care of keeping allocation/deallocation within the shared lib. I did some quick tryout below and it works on linux. The same example would not work with windows dll (it will work with windows dll if both exe and dll compiled with /MD to make use of the same heap).
std::vector<int> vec;
vec.push_back(42);
PassAVector(vec);
where PassAVector
resides in a shared lib
void PassAVector(std::vector<int> &vec)
{
vec.push_back(1); // These would cause reallocation
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
vec.push_back(5);
}
Does this mean shared libs on unix share the heap with the executables (equivalent to the /MD switch on windows)?
Is it possible to compile (some compiler flags) the shared lib (.so) or the executable on linux in such a way that they start using different heaps (/MT switch on windows) and this problem surfaces?
EDIT: Found this which seem to suggest passing STL across boundaries is okay as long long as the compiler is gcc.
Upvotes: 7
Views: 1520
Reputation: 81
Polymorphic objects created by a library should never outlive the library because they will get broken when the library is unloaded from memory.
It may sound obvious, but shared libraries can be unloaded. Such a design may be used by plugins for example.
The following scenario looks simple and harmless but triggers segmentation fault:
The problem is in the vtable of the polymorphic object. It is generated by the compiler in the address space of the library and when the library is gone the vtable is gone as well. But the real problem is that the segmentation fault is dormant and most of the time everything will pretend to work corectly until one day it will suddenly crash.
There are many implementation and operating system details that affect the behavior.
Below is the (incomplete) list of the things that affect the problem:
This affects all polymorphic classes. Virtual destructors are welcome. STL classes may easily use virtual calls under the hood - std::shared_ptr definitely does.
The example code that reproduces the problem. https://godbolt.org/z/scKEz47sY
I'm assuming C++ as the author mentioned STL containers in his question.
object.h
struct object
{
// object vtable resides in .so and is gone when .so is dlclosed
virtual void method() {}
};
plugin.cpp
#include "object.h"
extern "C" {
object* create_object() {
return new object();
}
}
main.cpp
#include <dlfcn.h>
#include "object.h"
int main(int, char*[])
{
void* so = dlopen("build/libplugin.so", RTLD_LAZY);
typedef object* (*create_object_method)(void);
create_object_method create_object = reinterpret_cast<create_object_method>(dlsym(so, "create_object"));
object* obj = create_object();
dlclose(so);
obj->method();
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(shared_mem)
add_library(plugin SHARED plugin.cpp)
add_executable(the_executable main.cpp)
target_link_libraries(the_executable dl)
Create the files in one directory and run:
mkdir build && cd build && cmake .. && make && ./the_executable
Upvotes: 1
Reputation: 21955
As long as you stick with Glibc or other "normal" allocators (jemalloc, tcmalloc, etc.) the heap state will be shared by all libraries and thus you'll be able to free memory allocated somewhere with malloc
anywhere you want.
In theory it may be possible to circumvent this. For example some library may be linked with custom implementation of malloc
/free
(via symbol scripts trickery of -Bsymbolic
) which has it's own private heap and thus will not interact well with other parts of your program. But I've never seen anything like this in real life.
STL containers are based on malloc
/free
so it is possible to pass/modify them across library boundaries as well. Of course different libraries may be compiled with different compilers and different incompatible versions of STL (e.g. libstdc++, libcxx, etc.) but their C++ container types would be different and compiler simply would not allow you to pass them across incompatible modules.
Upvotes: 4
Reputation: 12507
In C++ the normal way to change the heap is to overload the global operator new/delete.
In Windows that overload is restricted to the specific dll and thus you get the problems.
In Linux the overloaded global operator is normally truly global - so the first one wins, but if you really want specific heaps in them you can achieve that (and then you need the exported allocate and free-functions):
C++ custom global new/delete overriding system libraries
Upvotes: 1