Как проверить, есть ли утечки памяти в программе.

Всем доброго времени суток.
НаписАл я стек. Работает он нормально, но мне хочется проверить, есть ли где-нибудь утечки памяти. А именно в методе clear(). Как сделать это?

На всякий случай, код:


#ifndef STACK_H
#define STACK_H

//stack.hpp - простейший стек. just for fun :) 

#include <stdexcept>

class stack_except : public std::runtime_error
{
    public:
        stack_except( const char *error ):
            std::runtime_error( error )
        {
        }
};

template< typename stack_node_type >
struct stack_node
{
    stack_node_type data;
    stack_node< stack_node_type > *prev;
};

template< typename stack_t >
class stack
{
    public:
        stack();
        ~stack();

        void push( const stack_t& );
        void pop( stack_t& );
        void clear();

        size_t size() const;

        bool empty() const;

    private:
        stack_node< stack_t > *top;
        size_t _size;       
};

template< typename stack_t >
stack< stack_t > :: stack():
    top( NULL ),
    _size( 0 )
{   
}

template< typename stack_t >
stack< stack_t > :: ~stack()
{
    if ( !empty() )
        clear();
}

template< typename stack_t >
void stack< stack_t > :: push( const stack_t &data )
{
    stack_node< stack_t > *temp = new stack_node< stack_t >;
    temp->data = data;
    if ( empty() )
        temp->prev = NULL;
    else
        temp->prev = top;
    top = temp;
    _size++;
}

template< typename stack_t >
void stack< stack_t > :: pop( stack_t &data )
{
    if ( empty() )
        throw stack_except( "The stack is empty" );
    else
    {
        data = top->data;
        stack_node< stack_t > *temp = top->prev;
        delete top;
        top = temp;
        _size--;
    }
}

template< typename stack_t >
void stack< stack_t > :: clear()
{
    if ( !empty() )
    {
        while ( top )
        {
            stack_node< stack_t > *ptr = top->prev;
            delete top;
            top = ptr;
        } 
    }
}

template< typename stack_t >
size_t stack< stack_t > :: size() const
{
    return _size;
}

template< typename stack_t >
bool stack< stack_t > :: empty() const
{
    return top == NULL;
}

#endif //STACK_H

Если ты используешь MS VC++, то посмотри Обнаружение утечек памяти с помощью библиотеки CRT. Работают ли эти функции под другими компиляторами и/или осями — не знаю.

Можно попробовать сделать глобальную перегрузку операторов new и delete и взять все распределение памяти в свои руки. Но мне кажется что это еще более стремный вариант в смысле напороть с распределением памяти.

Во-первых, попробуй mtrace или посмотреть здесь. Линукс не использую, поэтому совет достаточно теоретический ))

Во-вторых... Меня заинтересовало предложение Макара по поводу перегрузки new и delete. Если в программе работа с кучей идёт только через эти операторы, то такой ход возможен. В противном случае, надо применять «тяжёлую артиллерию» в виде mtrace и подобных штук.

Я накидал некий код на тему перегрузки операторов. Вроде работает ))

Идея

Идея заключается в следующем. К каждому выделяемому блоку памяти присоединять блок служебной информации, который поможет отследить операции выделения/освобождения памяти.

Реализация.

При выделении памяти перегруженным оператором new выделяется памяти больше, чем заказывали, на размер структуры MBLOCK. В начале выделенного блока размещается структура MBLOCK, а дальше идёт место для данных, для которых собственно выделялась память из кучи. Вызывающему коду возвращается указатель на это место для данных. Так что вызывающий код ничего некошерного не замечает.

С помощью структуры MBLOCK и двух глобальных переменных организуется простейший список. В структуре содержится размер блока (без учёта служебной информации) и указатель на следующий блок. При вызове new в список добавляется элемент. При вызове delete из списка удаляется элемент (естественно без нарушения целостности списка).

Функция mem_report() выводит список блоков в текстовом виде в указанный поток. По этой информации можно увидеть неосвобождённые блоки памяти, т.е. зафиксировать утечку.

Для большей наглядности я в каждый перегруженный оператор добавил печать имени функции с доп. информацией.

Дополнительно в оператор delete добавлена печать диагностики попытки повторного освобождения блока памяти.

Компилировал под gcc version 4.8.1 (tdm64-2), Win7.

dmem.h

#ifndef __DMEM_H__
#define __DMEM_H__

#include <iostream>
#include <cstdlib>
#include <new>

void *operator new (std::size_t size) throw(std::bad_alloc);
void *operator new[] (size_t size) throw(std::bad_alloc);

void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) throw();

void operator delete (void *ptr) throw();
void operator delete[] (void *ptr) throw();

void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) throw();
void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) throw();

void mem_report(std::ostream &os);

#endif // __DMEM_H__

dmem.cpp

#include "dmem.h"

using namespace std;

struct MBLOCK {
    size_t size;
    MBLOCK *next;
};

MBLOCK *start_block = NULL;
MBLOCK *last_block = NULL;

void *alloc_memory(size_t size, bool raise_exept) {
    void *ptr = malloc(size + sizeof(MBLOCK));
    if (!ptr) {
        if (raise_exept) {
            throw std::bad_alloc();
        }
        else {
            return NULL;
        }
    }
    MBLOCK *new_block = static_cast<MBLOCK *>(ptr);
    new_block->size = size;
    new_block->next = NULL;
    if (last_block == NULL) {
        start_block = last_block = new_block;
    }
    else {
        last_block->next = new_block;
        last_block = new_block;
    }
    return ptr + sizeof(MBLOCK);
}

void free_memory(void *ptr) throw() {
    MBLOCK *block = static_cast<MBLOCK *>(ptr - sizeof(MBLOCK));
    MBLOCK *pmb = start_block;
    if (block != start_block) {
        while (pmb != NULL && pmb->next != block) {
            pmb = pmb->next;
        }
        if (pmb == NULL) {
            cerr << "** dmem ** attempt to delete inappropriate memory block\n";
            return;
        }
        pmb->next = block->next;
        if (block == last_block) {
            last_block = pmb;
        }
    }
    else {
        start_block = block->next;
        if (start_block == NULL) {
            last_block = NULL;
        }
    }
    cerr << "Freed " << block->size << " byte(s) at address " << ptr << endl;
    free(block);
}


void *operator new(size_t size) throw(std::bad_alloc) {
    cout << "** new ** " << size << endl;
    return alloc_memory(size, true);
}

void *operator new[](size_t size) throw(std::bad_alloc) {
    cout << "** new[] ** " << size << endl;
    return alloc_memory(size, true);
}

void* operator new(std::size_t size, const std::nothrow_t& nothrow_value) throw() {
    cout << "** new[] nothrow ** " << size << endl;
    return alloc_memory(size, false);
}

void* operator new[](std::size_t size, const std::nothrow_t& nothrow_value) throw() {
    cout << "** new[] nothrow ** " << size << endl;
    return alloc_memory(size, false);
}



void operator delete(void *ptr) throw() {
    cout << "** delete ** " << ptr << endl;
    free_memory(ptr);
}

void operator delete[](void *ptr) throw() {
    cout << "** delete[] ** " << ptr << endl;
    free_memory(ptr);
}

void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) throw() {
    cout << "** delete[] nothrow ** " << ptr << endl;
    free_memory(ptr);
}

void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) throw() {
    cout << "** delete[] nothrow ** " << ptr << endl;
    free_memory(ptr);
}




void mem_report(ostream &os) {
    os << "\n** mem_report **" << endl;
    size_t total_alloc = 0;
    MBLOCK *block = start_block;
    size_t counter = 0;
    while (block != NULL) {
        os << "Addr " << (static_cast<void *>(block) + sizeof(MBLOCK)) << " length " << block->size << " byte(s)" << endl;
        total_alloc += block->size;
        counter++;
        block = block->next;
    }
    os <<  "--\nTotal " << counter << " blocks." << endl
        << "Total allocated " << total_alloc << " byte(s)\n" << endl;
}

main.cpp

#include <iostream>
#include "dmem.h"

using namespace std;

int main()
{
    cout << "Memory leaks test\n\n";

    mem_report(cout);

    char *pch = new char;
    mem_report(cout);
    long *plong = new long;
    mem_report(cout);
    long long *plonglong = new long long;
    mem_report(cout);

    char *pcha = new char[50];
    mem_report(cout);
    int *pinta = new int[20];
    mem_report(cout);

    delete [] pinta;
    mem_report(cout);
    delete [] pcha;
    mem_report(cout);

    delete [] pinta;  // повторное освобождение блока памяти
    mem_report(cout);

    delete plong;
    mem_report(cout);
    delete pch;
    mem_report(cout);
    delete plonglong;
    mem_report(cout);

    return 0;
}

Чтобы переварить код от Cranium, мне потребовалось немало времени :)

В основном мне всё понятно, но мне не понятен вот этот момент:

void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) throw();

Что это значит и для чего это нужно?

Это форма операторов new, которая не возбуждает исключение. При недостатке памяти просто возвращается NULL.

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.