Verständnis für alternativen new-Op



  • Bin auf der Suche nach einem Speicherleck und würde gerne MemCheck von Bruce Eckel verwenden:

    http://kia.etel.ru/lib/thcpp/tic0304.html#Heading790

    Dazu zwei Fragen:

    1. Wie funktioniert das überhaupt. D.h. warum werden allein durch das einbinden der cpp-Datei new, delete, malloc etc. ersetzt?

    2. Was fehlt ist new[]. Ich hab das ganz banal implementiert mit

    void* operator new[](size_t sz) {
      void* p = getmem(sz);
      return MEMBAG_.add(p, MemBag::New);
    }
    
    void*
    operator new[](size_t sz, char* file, int line) {
      void* p = getmem(sz);
      return MEMBAG_.add(p, MemBag::New, file,line);
    }
    
    void operator delete[](void* v) {
      MEMBAG_.validate(v, MemBag::New);
    }
    

    Also einfach per copy und Paste und ein '[]' hinten ran. Scheint zu funktionieren. Was mir aber nicht ganz geheuer ist. Wer oder was sorgt denn in dem neuen new-Operator dafür, dass der Ctor aufgerufen wird, bzw. bei new[] dass der Ctor für jedes Objekt einmal aufgerufen wird?



  • Der Clou an der Sache ist, dass es im Grunde zwei verschiedene new's gibt:

    einmal der operator new, den du in der Klasse überschreiben kannst. Der sorgt nur dafür, dass der Speicher zur Verfügung gestellt wird.
    Und einmal das new, das du im Programmcode aufrufst. Schreibst du zum Beispiel

    new Object(bla)
    

    so macht der Compiler folgendes:

    1. Der operator new von Object wird aufgerufen (oder halt der standard operator new falls nicht überschrieben)
    2. Der Speicherbereich der vom operator zurückgegeben wird wird mit dem entsprechenden Ctor von Object initialisiert
    3. Der Zeiger auf den Speicherbereich wird zurückgegeben.

    Vergleiche dazu auch Scott Meyers, "effektiv C++ programmieren", Richtlinien 5-10



  • pumuckl schrieb:

    Der Clou an der Sache ist, dass es im Grunde zwei verschiedene new's gibt:

    einmal der operator new, den du in der Klasse überschreiben kannst. Der sorgt nur dafür, dass der Speicher zur Verfügung gestellt wird.
    Und einmal das new, das du im Programmcode aufrufst. Schreibst du zum Beispiel

    new Object(bla)
    

    so macht der Compiler folgendes:

    1. Der operator new von Object wird aufgerufen (oder halt der standard operator new falls nicht überschrieben)
    2. Der Speicherbereich der vom operator zurückgegeben wird wird mit dem entsprechenden Ctor von Object initialisiert
    3. Der Zeiger auf den Speicherbereich wird zurückgegeben.

    Vergleiche dazu auch Scott Meyers, "effektiv C++ programmieren", Richtlinien 5-10

    👍 👍



  • ok. So weit zum Verständnis. Jetzt zur Praxis :-0

    //---------------------------------
    
    //: C26:MemCheck.cpp {O}
    // Memory allocation tester
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    using namespace std;
    // MemCheck.h must not be included here
    
    // Output file object using cstdio
    // (cout constructor calls malloc())
    class OFile {
      FILE* f;
    public:
      OFile(char* name) : f(fopen(name, "w")) {}
      ~OFile() { fclose(f); }
      operator FILE*() { return f; }
    };
    extern OFile memtrace;
    // Comment out the following to send all the
    // information to the trace file:
    #define memtrace stdout
    
    const unsigned long _pool_sz = 5000000L;
    static unsigned char _memory_pool[_pool_sz];
    static unsigned char* _pool_ptr = _memory_pool;
    
    void* getmem(size_t sz) {
      if(_memory_pool + _pool_sz - _pool_ptr < sz) {
        fprintf(stderr,
               "Out of memory. Use bigger model\n");
        exit(1);
      }
      void* p = _pool_ptr;
      _pool_ptr += sz;
      return p;
    }
    
    // Holds information about allocated pointers:
    class MemBag {
    public:
      enum type { Malloc, New, NewArray };
    private:
      char* typestr(type t) {
        switch(t) {
          case Malloc: return "malloc";
          case New: return "new";
          case NewArray: return "new[]";
          default: return "?unknown?";
        }
      }
      struct M {
        void* mp;  // Memory pointer
        type t;     // Allocation type
        char* file; // File name where allocated
        int line;  // Line number where allocated
        M(void* v, type tt, char* f, int l)
          : mp(v), t(tt), file(f), line(l) {}
      }* v;
      int sz, next;
      static const int increment = 50 ;
    public:
      MemBag() : v(0), sz(0), next(0) {}
      void* add(void* p, type tt = Malloc,
                char* s = "library", int l = 0) {
        if(next >= sz) {
          sz += increment;
          // This memory is never freed, so it
          // doesn't "get involved" in the test:
          const int memsize = sz * sizeof(M);
          // Equivalent of realloc, no registration:
          void* p = getmem(memsize);
          if(v) memmove(p, v, memsize);
          v = (M*)p;
          memset(&v[next], 0,
                 increment * sizeof(M));
        }
        v[next++] = M(p, tt, s, l);
        return p;
      }
      // Print information about allocation:
      void allocation(int i) {
        fprintf(memtrace, "pointer %p"
          " allocated with %s",
          v[i].mp, typestr(v[i].t));
        if(v[i].t == New)
          fprintf(memtrace, " at %s: %d",
            v[i].file, v[i].line);
        fprintf(memtrace, "\n");
      }
      void validate(void* p, type T = Malloc) {
        for(int i = 0; i < next; i++)
          if(v[i].mp == p) {
            if(v[i].t != T) {
              allocation(i);
              fprintf(memtrace,
              "\t was released as if it were "
              "allocated with %s \n", typestr(T));
            }
            v[i].mp = 0;  // Erase it
            return;
          }
        fprintf(memtrace,
        "pointer not in memory list: %p\n", p);
      }
      ~MemBag() {
        for(int i = 0; i < next; i++)
          if(v[i].mp != 0) {
            fprintf(memtrace,
            "pointer not released: ");
            allocation(i);
          }
      }
    };
    extern MemBag MEMBAG_;
    
    void* malloc(size_t sz) {
      void* p = getmem(sz);
      return MEMBAG_.add(p, MemBag::Malloc);
    }
    
    void* calloc(size_t num_elems, size_t elem_sz) {
      void* p = getmem(num_elems * elem_sz);
      memset(p, 0, num_elems * elem_sz);
      return MEMBAG_.add(p, MemBag::Malloc);
    }  
    
    void* realloc(void* block, size_t sz) {
      void* p = getmem(sz);
      if(block) memmove(p, block, sz);
      return MEMBAG_.add(p, MemBag::Malloc);
    }
    
    void free(void* v) { 
      MEMBAG_.validate(v, MemBag::Malloc);
    }
    
    void* operator new(size_t sz) {
      void* p = getmem(sz);
      return MEMBAG_.add(p, MemBag::New);
    }
    
    void*
    operator new(size_t sz, char* file, int line) {
      void* p = getmem(sz);
      return MEMBAG_.add(p, MemBag::New, file,line);
    }
    
    void operator delete(void* v) {
      MEMBAG_.validate(v, MemBag::New);
    }
    
    void* operator new[](size_t sz) {
      void* p = getmem(sz);
      return MEMBAG_.add(p, MemBag::NewArray);
    }
    
    void*
    operator new[](size_t sz, char* file, int line) {
      void* p = getmem(sz);
      return MEMBAG_.add(p, MemBag::NewArray, file,line);
    }
    
    void operator delete[](void* v) {
      MEMBAG_.validate(v, MemBag::NewArray);
    }
    
    MemBag MEMBAG_;
    // Placed here so the constructor is called
    // AFTER that of MEMBAG_ :
    #ifdef memtrace
    #undef memtrace
    #endif
    OFile memtrace("memtrace.out");
    // Causes 1 "pointer not in memory list" message
    
    //---------------------------------
    
    #include <string>
    
    int main() {
       std::string
      str("hallo");
    
    } ///:~
    

    Das Programm führt beim Beenden zur Meldung 'pointer not in memory list: ..."

    Anscheinend wird beim erzeugen des Strings nicht das new verwendet, das in der memcheck-Bib definiert ist. Hängt das irgendwie mit der initialisierungreihenfolge globaler Objekte zusammen? Habt ihr schon mal auf die Art versucht ein Speicherleck zu finden?

    Edit: Die MemCheck-Klassen direkt vor die Main-Klasse gemacht. Man sollte jetzt den Code per Copy and Paste rausnehmen und in den Compiler werfen können. Falls es jemand ausprobieren möchte 🙂



  • Na super. Hab bisher mit dem C++Builder gearbeitet. Wenn ich das ganze in VC++7.1 ausprobiere, krieg ich gleich Linker-Fehler:

    memcheck.obj : error LNK2005: _main bereits in main.obj definiert
    LIBCD.lib(dbgheap.obj) : error LNK2005: _malloc bereits in memcheck.obj definiert
    LIBCD.lib(dbgheap.obj) : error LNK2005: _calloc bereits in memcheck.obj definiert
    LIBCD.lib(dbgheap.obj) : error LNK2005: _realloc bereits in memcheck.obj definiert
    LIBCD.lib(dbgheap.obj) : error LNK2005: _free bereits in memcheck.obj definiert

    (memcheck.obj ist das Modul, in dem der Code, den ich oben vor der main-Funktion gepostet hab, drin ist).

    🙄

    Irgendwie bin ich ein bisschen verzweifelt. Wir haben ne Server-Componente, bei der der Speicher kurzfristig unmerklich, über Stunden aber deutlich steigt, es könnten mehrere DLLs dran schuld sein und ich hab echt keinen Plan mehr, wie ich das finden soll 😞

    Alles was ich an Profilern und Bound-Checkern ausprobiere, wirft mir zig Warnungen um die Ohren, die aber z.T. einfach nicht stimmen könnnen (s.o.) und meistens ziehmlich unnachvollziebar sind. Und dann hab ich auch noch Code, der im einen Compiler compiliert und im andern nicht. Wie soll ich da auf nen grünen Zweig kommen????


Anmelden zum Antworten