It can be done at the expense of simplicity and therefore performance. Instead of storing pointers inside the shared memory, you have to store offsets from the beginning of the region instead. Then, when you want to "dereference" one of these, you add the offset to the pointer-to-the-shared-region.
To avoid errors, I would make a special type for this, depending on the actual language you're using
C
//seriously, this is one situation where I would find a global justified
region_ptr region;
//store these instead of pointers inside the memory region
struct node_offset {ptrdiff_t offset};
//used to get a temporary pointer from an offset in a region
//the pointer is invalidated when the memory is reallocated
//the pointer cannot be stored in the region
node* get_node_ptr(node_offset offset)
{return (node*)((char*)region+offset.offset);}
//used to get an offset from a pointer in a region
//store offsets in the region, not pointers
node_offset set_node_ptr(region* r, node* offset)
{node_offset o = {(char*)offset.offset-(char*)region}; return o;}
C++
//seriously, this is one situation where I would find a global justified
region_ptr region;
//store these in the memory region
//but you can pretend they're actual pointers
template<class T>
struct offset_ptr {
offset_ptr() : offset(0) {}
T* get() const {return (T*)((char*)region + offset);}
void set(T* ptr) {offset = (char*)ptr - (char*)region;}
offset_ptr(T* ptr) {set(ptr);}
offset_ptr& operator=(T* ptr) {set(ptr); return *this;}
operator T*() const {return get();}
T* operator->() const {return get();}
T& operator*() const {return *get();}
private:
ptrdiff_t offset;
};
template<class T>
struct offset_delete {
typedef offset_ptr<T> pointer;
void operator()(offset_ptr<T> ptr) const {ptr->~T();}
};
//std::unique_ptr<node, offset_delete<node>> node_ptr;