Problem mit operator new



  • Hallo,

    ich habe ein verzwicktes Problem. Alle meine Heapallokationen laufen über eine Singleton Klasse MemoryManager. Der MemoryManager hat (aus Debug/Trackgründen) eine std::map als private Member. Vereinfacht gesagt:

    class MemoryManager {
    private:
        std::map<void*, AllocInfo>	mAllocMap;
    
        MemoryManager() { }
        MemoryManager(const MemoryManager& rhs) { }
        MemoryManager& operator=(const MemoryManager& rhs) { }
       ~MemoryManager() { }
    
    public:
        static MemoryManager& MemoryManager::getInstance() {
            static MemoryManager instance;
            return instance;
        }
    
        void* allocate(size_t size);
        void  deallocate(void* p);
    
        void addLogEntry(...);
    };
    

    Mein operator new sieht (vereinfacht) so aus:

    void* operator new(size_t size) {
        void* p = MemoryManager::getInstance().allocate(size);
        MemoryManager::getInstance().addLogEntry(...); // Zeile A
        return p;
    }
    

    So, wenn ich jetzt in meinem main() ein new Foo; mache, dann stürzt er in Zeile A ab.
    Meine Vermutung (!) ist, dass das der Grund ist: new -> operator new wird aufgerufen -> MemoryManager::getInstance() wird aufgerufen -> in getInstance() wird ein statisches lokales Objekt vom Typ MemoryManager angelegt, ergo wird der MemoryManager Ctor aufgerufen. Der ist zwar leer, aber da MemoryManager als Member eine std::map hat, wird der std::map Ctor aufgerufen und der macht offenbar ein new. Jetzt landen wir wieder in meinem operator new -> er holt sich die eine Singleton Instanz (die aber noch nicht fertig initialisiert ist!) und crasht dann beim Methodenaufruf (da MemoryManager noch nicht fertig intialisiert ist).

    1. Ist meine Analyse richtig? Ist das das Problem?
    2. Wie kann ich das fixen? Das ist irgendwie ein Teufelskreis. MemoryManager Ctor ruft std::map Ctor auf und der benutzt new, was wiederrum meinen MemoryManager braucht, der aber noch nicht fertig ist. Wie löse ich das?

  • Mod

    Anstatt hier groß zu spekulieren: Solchen Fehler kommt man recht leicht mit Hilfe eines Debuggers auf die Spur.



  • Okay, war nicht richtig... habs mir gerade nochmal angeschaut... Sorry.
    Ich mache das normallerweise etwas anderst, aber so ists fast schöner... =)...

    L.g.



  • SeppJ schrieb:

    Anstatt hier groß zu spekulieren: Solchen Fehler kommt man recht leicht mit Hilfe eines Debuggers auf die Spur.

    Das sagt sich so leicht. So leicht ist der Output auch nicht zu interpretieren:

    App.exe!std::_Tree<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >::_Lbound(void * const & _Keyval=0x0000000001c078b0) Line 1742 + 0xa bytes C++
    App.exe!std::_Tree<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >::lower_bound(void * const & _Keyval=0x0000000001c078b0) Line 1450 + 0xf bytes C++
    > App.exe!std::map<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> > >::operator[](void * const & _Keyval=0x0000000001c078b0) Line 211 + 0x1a bytes C++
    App.exe!ple::MemManager::createAllocInfo(void * p=0x0000000001c078b0, unsigned int size=16, const char * desc=0x000000013f996c4f, MEM_TYPE memType=MEM_EXT, bool isArray=false, const char * srcFilename=0x000000013f996c4e, int line=-1) Line 73 + 0x77 bytes C++
    App.exe!operator new(unsigned int64 size=16) Line 225 C++
    App.exe!std::_Allocatestd::\_Container\_proxy(unsigned __int64 _Count=1, std::_Container_proxy * __formal=0x0000000000000000) Line 36 + 0x22 bytes C++
    App.exe!std::allocatorstd::\_Container\_proxy::allocate(unsigned __int64 _Count=1) Line 188 C++
    App.exe!std::_Tree_nod<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >::_Tree_nod<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >(const std::less<void *> & _Parg=less, std::allocator<std::pair<void * const,ple::MemManager::AllocInfo> > * _Al=0x000000000016f760) Line 492 + 0xf bytes C++
    App.exe!std::_Tree_val<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >::_Tree_val<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >(const std::less<void *> & _Parg=less, std::allocator<std::pair<void * const,ple::MemManager::AllocInfo> > * _Al=0x000000000016f7b0) Line 542 + 0x5c bytes C++
    App.exe!std::_Tree<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >::_Tree<std::_Tmap_traits<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> >,0> >(const std::less<void *> & _Parg=less, const std::allocator<std::pair<void * const,ple::MemManager::AllocInfo> > & _Al={...}) Line 699 C++
    App.exe!std::map<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> > >::map<void * __ptr64,ple::MemManager::AllocInfo,std::less<void * __ptr64>,std::allocator<std::pair<void * __ptr64 const,ple::MemManager::AllocInfo> > >() Line 107 C++
    App.exe!ple::MemManager::MemManager() Line 32 C++
    App.exe!ple::MemManager::getInstance() Line 22 + 0x28 bytes C++
    App.exe!operator new(unsigned __int64 size=104, const char * desc=0x000000013f99696d, MEM_TYPE memType=MEM_OBJ, const char * srcFilename=0x000000013f996960, int line=43) Line 187 + 0x5 bytes C++
    App.exe!WinMain(HINSTANCE
    * hInstance=0x000000013f8f0000, HINSTANCE__ * hPrevInstance=0x0000000000000000, char * szCmdLine=0x0000000000303ab0, int iCmdShow=1) Line 43 + 0x26 bytes C++

    So wie ich das sehe stürzt er beim map operator[] in dieser Funktion ab:

    void MemManager::createAllocInfo(void* p, unsigned int size, const char* desc, MEM_TYPE memType, bool isArray, const char* srcFilename, int line) {
    
       if(mLockCount > 0)
          return;
    
        MemManager::AllocLock lock(*this); // Disable alloc tracking
        mAllocMap[p] = AllocInfo(desc, memType, isArray, size, srcFilename, line); // hier stürzt er ab
    }
    

    Kann es sein, dass die Map noch garnicht fertig initialisiert ist an der Stelle, an der ich map::operator[] aufrufe?

    @AlexanderKiebler: Keine Ahnung was du meinst. Das ist einfach ein Meyer Singleton und ich will definitiv keinen Zeiger benutzen.



  • (Nicht hübsch:) Du kannst der map einen Allokator mitgeben. Da baust Du einen, der halt nur malloc benutzt.



  • Spontan fällt mir nur ein: Der std::map kann man einen Allocator mit auf den Weg geben. Dort müsste man einen entsprechenden übergeben. Weiß allerdings nicht ob es schon geeignete vordefinierte Allocatoren dafür gibt - sonst selber einen backen.



  • Hm, das gefällt mir alles nicht.

    Könnte mir dennoch jemand mal sagen, ob meine Einschätzung richtig ist? Ich würde nämlich auch gerne was aus dem Problem lernen und wissen WIESO er abstürzt beim map::operator[].


  • Mod

    Karya22 schrieb:

    Hm, das gefällt mir alles nicht.

    Könnte mir dennoch jemand mal sagen, ob meine Einschätzung richtig ist? Ich würde nämlich auch gerne was aus dem Problem lernen und wissen WIESO er abstürzt beim map::operator[].

    Weil du auf den []-Operator der map zugreifst, bevor diese fertig konstruiert ist.
    Das ist ein prinzipielles Problem: mit der map speicherst du Metadaten in einem separaten Speicherbereich. Das setzt aber veraus dass dieser separate Speicherbereich nicht auch noch verwaltet wird, andernfalls hast du ein Rekursionsproblem. Je nachdem wie die map implementiert ist, entweder bei der Konstruktion oder spätestens wenn du zum ersten mal eine Allokation durchführst.

    Die Lösung besteht entweder in einem map-Allokator, der deine eigene Speicherverwaltung umgeht, oder in einem anderem Design, das die Metadaten eben nicht in einem gesonderten Bereich ablegt.



  • camper schrieb:

    Karya22 schrieb:

    Hm, das gefällt mir alles nicht.

    Könnte mir dennoch jemand mal sagen, ob meine Einschätzung richtig ist? Ich würde nämlich auch gerne was aus dem Problem lernen und wissen WIESO er abstürzt beim map::operator[].

    Weil du auf den []-Operator der map zugreifst, bevor diese fertig konstruiert ist.
    Das ist ein prinzipielles Problem: mit der map speicherst du Metadaten in einem separaten Speicherbereich. Das setzt aber veraus dass dieser separate Speicherbereich nicht auch noch verwaltet wird, andernfalls hast du ein Rekursionsproblem. Je nachdem wie die map implementiert ist, entweder bei der Konstruktion oder spätestens wenn du zum ersten mal eine Allokation durchführst.

    Die Lösung besteht entweder in einem map-Allokator, der deine eigene Speicherverwaltung umgeht, oder in einem anderem Design, das die Metadaten eben nicht in einem gesonderten Bereich ablegt.

    Danke für die hilfreiche Antwort! 👍

    Aber ein eigener Map Alloaktor geht doch dann nur für die map. Ich will ja ALLE new's kontrollieren, auch ein new int, oder new string etc.
    Hast du vllt einen kontreten Vorschlag, wie das ohne "gesonderten Bereich" aussehen könnte?



  • Einen eigenen map-Allokator kannst du ja für genau diese eine map festlegen.

    Und die Lösung besteht ja darin, daß du einen Allokator ohne Verwendung von new nimmst und deine mAllocMap damit ausstattest - d.h. alle new-Aufrufe außer aus dieser Map werden durch deinen operator new geleitet.



  • Kann es sein, dass es saukompliziert ist, so einen eigenen Allocator zu schreiben.
    Hab gerade das hier ergoogelt: http://www.codeproject.com/KB/cpp/allocator.aspx

    und mal ernsthaft: WTF?! Der schreibt da zig Klassen mit Traits und Policies und was weiß ich noch alles. Muss das so komplex sein?:(



  • Ja, man kann es wirklich bis zum Extrem treiben - aber für dich dürfte die erste Version ausreuchen. Du mußt nur die Methoden allocate() und deallocate() anpassen, so daß sie mit malloc() bzw. free() arbeiten.



  • CStoll schrieb:

    Ja, man kann es wirklich bis zum Extrem treiben - aber für dich dürfte die erste Version ausreuchen. Du mußt nur die Methoden allocate() und deallocate() anpassen, so daß sie mit malloc() bzw. free() arbeiten.

    Danke!

    Hab mir jetzt mal schnell einen simplen Allocator geschrieben. Das hier sind wohl die wichtigsten Methode:

    inline pointer allocate(size_type cnt,  typename std::allocator<void>::const_pointer = 0) { 
        return reinterpret_cast<pointer>(malloc(cnt * sizeof(T))); 
    }
    
    inline void deallocate(pointer p, size_type) { 
        free(p);
    }
    
    inline size_type max_size() const { 
        return std::numeric_limits<size_type>::max() / sizeof(T);
    }
    
    inline void construct(pointer p, const T& t) { new(p) T(t); }
    inline void destroy(pointer p) { p->~T(); }
    

    Sieht das ok aus? Meine einzige Frage dazu: In construct() benutze ich new(p). Aber das ruft ja NICHT meinen globalen operator new(size_t s) auf, sondern das ist ein placement new, das operator new(size_t s, void* p) aufruft, oder?

    Noch was: Wenn ich in meinem MemoryManager jetzt die Map mit meinem Custom Allocator erzeugen will:

    typedef MemManagerMapAllocator< std::pair<void*, AllocInfo> > MapAllocator;
    std::map<void*, AllocInfo, std::less<void*>, MapAllocator>	mAllocMap;
    

    kriege ich folgende kryptische Fehlermeldung:

    D:\Microsoft Visual Studio 10.0\VC\include\xtree(698): error C2664: 'std::_Tree_val<_Traits>::_Tree_val(const std::less<_Ty> &,ple::MemManagerMapAllocator<T>)' : cannot convert parameter 2 from 'const ple::MemManagerMapAllocator<T>' to 'ple::MemManagerMapAllocator<T>'
    1> with
    1> [
    1> _Traits=std::_Tmap_traits<void *,ple::MemManager::AllocInfo,std::less<void *>,ple::MemManager::MapAllocator,false>,
    1> _Ty=void *,
    1> T=std::pair<void *const ,ple::MemManager::AllocInfo>
    1> ]
    1> and
    1> [
    1> T=std::pair<void *const ,ple::MemManager::AllocInfo>
    1> ]
    1> and
    1> [
    1> T=std::pair<void *const ,ple::MemManager::AllocInfo>
    1> ]
    1> Constructor for class 'ple::MemManagerMapAllocator<T>' is declared 'explicit'
    1> with
    1> [
    1> T=std::pair<void *const ,ple::MemManager::AllocInfo>
    1> ]
    1> D:\Microsoft Visual Studio 10.0\VC\include\xtree(695) : while compiling class template member function 'std::_Tree<_Traits>::_Tree(const std::less<_Ty> &,const ple::MemManagerMapAllocator<T> &)'
    1> with
    1> [
    1> _Traits=std::_Tmap_traits<void *,ple::MemManager::AllocInfo,std::less<void *>,ple::MemManager::MapAllocator,false>,
    1> _Ty=void *,
    1> T=std::pair<void *const ,ple::MemManager::AllocInfo>
    1> ]
    1> D:\Microsoft Visual Studio 10.0\VC\include\map(81) : see reference to class template instantiation 'std::_Tree<_Traits>' being compiled
    1> with
    1> [
    1> _Traits=std::_Tmap_traits<void *,ple::MemManager::AllocInfo,std::less<void *>,ple::MemManager::MapAllocator,false>
    1> ]
    1> E:\Programmieren\C++\MemManager.h(174) : see reference to class template instantiation 'std::map<_Kty,_Ty,_Pr,_Alloc>' being compiled
    1> with
    1> [
    1> _Kty=void *,
    1> _Ty=ple::MemManager::AllocInfo,
    1> _Pr=std::less<void *>,
    1> _Alloc=ple::MemManager::MapAllocator
    1> ]

    Was ist der Fehler?



  • Karya22 schrieb:

    Sieht das ok aus? Meine einzige Frage dazu: In construct() benutze ich new(p). Aber das ruft ja NICHT meinen globalen operator new(size_t s) auf, sondern das ist ein placement new, das operator new(size_t s, void* p) aufruft, oder?

    Ja, diese Methoden sehen auf den ersten Blick OK aus, also liegt der Fehler vermutlich woanders. Und ja, construct*( verwendet nicht deinen operator new, sondern das placement new.

    Noch was: Wenn ich in meinem MemoryManager jetzt die Map mit meinem Custom Allocator erzeugen will:

    typedef MemManagerMapAllocator< std::pair<void*, AllocInfo> > MapAllocator;
    std::map<void*, AllocInfo, std::less<void*>, MapAllocator>	mAllocMap;
    

    kriege ich folgende kryptische Fehlermeldung:

    Das sieht so aus, als hättest du irgendwo ein const zu viel oder zu wenig.



  • CStoll schrieb:

    Noch was: Wenn ich in meinem MemoryManager jetzt die Map mit meinem Custom Allocator erzeugen will:

    typedef MemManagerMapAllocator< std::pair<void*, AllocInfo> > MapAllocator;
    std::map<void*, AllocInfo, std::less<void*>, MapAllocator>	mAllocMap;
    

    kriege ich folgende kryptische Fehlermeldung:

    Das sieht so aus, als hättest du irgendwo ein const zu viel oder zu wenig.

    Hm ja. Ich versteh nur nicht wo. Er bringt auch nicht irgend eine Zeile, in der der Fehler ist. Wenn ich auf die Fehlermeldung doppelt klicke, springt VS in diesen Code (Datei xtree):

    explicit _Tree(const key_compare& _Parg,
    		const allocator_type& _Al)
    		: _Mybase(_Parg, _Al)
    		{	// construct empty tree
    		}
    

    Zur Vollständigkeit hier mein Allocator:

    template<typename T>
    class MemManagerMapAllocator {
    public : 
        typedef T value_type;
        typedef value_type* pointer;
        typedef const value_type* const_pointer;
        typedef value_type& reference;
        typedef const value_type& const_reference;
        typedef std::size_t size_type;
        typedef std::ptrdiff_t difference_type;
    
    public : 
        template<typename U>
        struct rebind {
            typedef MemManagerMapAllocator<U> other;
        };
    
    public : 
        inline explicit MemManagerMapAllocator() {}
        inline ~MemManagerMapAllocator() {}
        inline explicit MemManagerMapAllocator(MemManagerMapAllocator const&) {}
        template<typename U>
        inline explicit MemManagerMapAllocator(MemManagerMapAllocator<U> const&) {}
    
        inline pointer address(reference r) { return &r; }
        inline const_pointer address(const_reference r) { return &r; }
    
        inline pointer allocate(size_type cnt,  typename std::allocator<void>::const_pointer = 0) { 
         return reinterpret_cast<pointer>(malloc(cnt * sizeof(T))); 
    }
    
        inline void deallocate(pointer p, size_type) { 
            free(p); 
        }
    
        inline size_type max_size() const { 
            return std::numeric_limits<size_type>::max() / sizeof(T);
     }
    
        inline void construct(pointer p, const T& t) { new(p) T(t); }
        inline void destroy(pointer p) { p->~T(); }
    
        inline bool operator==(MemManagerMapAllocator const&) { return true; }
        inline bool operator!=(MemManagerMapAllocator const& a) { return !operator==(a); }
    };
    

    Jemand eine Idee, wo da mein Fehler ist? 😕


  • Mod

    Lass das explizit weg. Wie kommt man eigentlich auf die Idee, das routinemäßig zu machen, insbes. mit Kopierkonstruktoren?
    allocate sollte static_cast benutzen:

    inline pointer allocate(size_type cnt, const_pointer = 0) { 
         return static_cast<pointer>(malloc(cnt * sizeof(T)));
    }
    

    construct sollte einen cast enthalten:

    inline void construct(pointer p, const T& t) { new(static_cast<void*>(p)) T(t); }
    


  • Vielen Dank! Die Fehler sind weg!

    Äh noch was. Hab mir 3 Beispielcodes für Custom Allocators ergoogelt und irgendwie machen alle 3 recht unterschiedliche Sachen. Offenbar ist das noch immer so ein bißchen ein Mysterium. 😃
    Einer hat z.B. von public std::allocator<T> abgeleitet. Sollte man das machen? Bringt das irgendwas?



  • Tja, da sieht man mal wieder, daß viele Wege nach Rom führen können 😉

    Karya22 schrieb:

    Einer hat z.B. von public std::allocator<T> abgeleitet. Sollte man das machen? Bringt das irgendwas?

    Normalerweise hast du im Allokator ziemlich viele Sachen, die sich nicht groß bei verschiedenen Implementationen unterscheiden (construct(), destroy(), die ganzen typedef's). In std::allokator<> sind die alle schon enthalten, also würdest du dir damit ein wenig der Arbeit einsparen.


  • Mod

    Entscheidend ist nicht die Definition des Standardallokators sondern die allgemeinen Anforderungen an Allokatoren (20.1.5)



  • camper schrieb:

    construct(pointer p, const T& t) { new(static_cast<void*>(p)) T(t); }
    

    Nach void* kann implizit gecastet werden, daher ist dieser Cast nicht nötig.


Anmelden zum Antworten