Обнаружение утечки памяти в подсчитанных объектах

Я пытаюсь напечатать, на какой строке называются addref и release. Вот код

В приведенном ниже коде я создал класс ReferenceCount, основные функции которого увеличивают и уменьшают количество ссылок.
Класс Referencemanager отслеживает количество ссылок и удаляет объект, как только он достигает 0.

Test1 — это тестовый класс. В основном я создаю указатель Test1 и оборачиваю его классом CReferenceManager. Теперь при создании CReferenceManager вызывается класс AddRef и при уничтожении вызывается Release.

Если есть утечка памяти, тогда было бы легче определить, смогу ли я распечатать числа FILE и LINE, когда AddRef и Release вызвали с подсчетом ссылок в этой точке.

Если есть способ, которым я могу напечатать номер ФАЙЛА и ЛИНИИ, откуда вызывается AddRef и Release. Одним из способов является то, что я могу перезаписать AddRef и Release в производных классах и принтерах FILE и LINE.

//ReferenceCount.h
#include <string>
#include <Windows.h>

using namespace std;
class CReferenceCount
{
public:
CReferenceCount();
virtual ~CReferenceCount();
virtual void AddRef();
virtual bool Release();private:
LONG m_ref;

};// RefCount.cpp
//

#include "stdafx.h"#include "ReferenceCount.h"

CReferenceCount::CReferenceCount():m_ref(0)
{
AddRef();

}

CReferenceCount::~CReferenceCount()
{
}

void CReferenceCount::AddRef()
{
InterlockedIncrement(&m_ref);
}

bool CReferenceCount::Release()
{
if (InterlockedDecrement(&m_ref) == 0)
{
delete this;
return true;
}

return false;
}//ReferenceManager.h
#include <string>
#include <Windows.h>

using namespace std;
class CReferenceCount
{
public:
CReferenceCount();
virtual ~CReferenceCount();
virtual void AddRef();
virtual bool Release();private:
LONG m_ref;

};

//test.cpp
#include "stdafx.h"#include "ReferenceCount.h"#include "RefManager.h"#include <iostream>
using namespace std;

class Test1: public CReferenceCount
{
public:
Test1(){}
~Test1(){}

private :
int m_i;
};

void main()
{
Test1 *pTest= new Test1();
CReferenceManager<Test1> testRef(pTest);

}

Similare вопросы, которые я отправил
найти, кто создает объект с помощью умного указателя
Шаблон проектирования для обнаружения утечек памяти для интеллектуальных указателей с подсчетом ссылок

но ни один из ответов не дает правильного объяснения для решения этой проблемы,

6

Решение

Единственный способ — определить макросы для вызова AddRef и Release, поскольку у функций нет способа узнать изнутри, откуда они вызываются. Таким образом, вы могли бы использовать что-то вроде.

#define RELEASE(obj) cout << __LINE__ << ":" << __FILE__ << endl; (obj).Release();

Кроме того, разные компиляторы имеют разные предопределенные макросы; если переносимость является проблемой, это то, что вы должны учитывать при написании кода, как указано выше. Справочник MSDN (2003)

Учитывая ваши комментарии ниже, я мог бы предложить другое несколько хакерское решение. Возможно, вы не сможете увидеть, где публикуется ваша ссылка, но вы можете получить больше информации о том, где она была создана, а какие не публикуются должным образом.

template <typename T>
struct CReferenceManager
{
CReferenceManager(const T & _obj, const string & _file, int _line) : mObj(_obj), mFile(_file), mLine(_line)
{
cout << "Constructing from " << _file << ":" << _line << endl;
CReferenceManager::sObjects[make_pair(mFile, mLine)]++;
mObj.addRef();
}

~CReferenceManager()
{
cout << "Destructing object created at " << mFile << ":" << mLine << endl;
CReferenceManager::sObjects[make_pair(mFile, mLine)]--;
mObj.Release();
}

static map<pair<string, int>, int> sObjects;
string mFile;
int mLine;
T obj;
}

int main()
{
...
// Cycle through sObjects before return, note any unreleased entries
return 0;
}

Обратите внимание, что это просто псевдокод; Я сомневаюсь, что он компилируется или работает из коробки!

6

Другие решения

Никогда не следует явно выделять или освобождать ссылки в своем собственном коде, поэтому сохранение исходного файла и строки, в которой ссылки увеличиваются или уменьшаются, не поможет вам вообще, поскольку они всегда будут (должны!) Находиться внутри управления подсчетом ссылок. код.

Вы не включили исходный код в свой класс CReferenceManager, но, исходя из вашего описания, он является оберткой для ссылочного подсчитанного объекта. Это правильно? Правильная реализация этого объекта CReferenceManager должна гарантировать, что:

  • конструктор, который принимает пустой указатель, хранит указатель и не меняет счетчик ссылок (поскольку ваш класс CReferenceCount создает объект с одной ссылкой)
  • ссылка всегда уменьшается в деструкторе
  • ссылка увеличивается в конструкторе копирования
  • ссылка на правый объект увеличивается, а ссылка на левый объект уменьшается в операторе присваивания
  • никакие явные методы увеличения / уменьшения не должны быть доступны
  • метод operator -> () должен возвращать указатель на объект
  • не должно быть прямого способа отсоединить подсчитанный объект ссылки от экземпляра CReferenceManager, которому он принадлежит. Единственный способ — через присвоение нового объекта с подсчетом ссылок.

Кроме того, вы хотели бы сделать AddRef() а также Release() методы в вашем классе CReferenceCount private и делают их доступными только для класса CReferenceManager через класс дружбы.

Если вы будете следовать приведенным выше правилам в своем классе CReferenceManager, то сможете избежать утечек или других проблем с памятью, обеспечив всем доступ к объекту через оболочку CReferenceManager, размещенную в стеке. Другими словами:

Чтобы создать новый счетный объект, на который есть ссылка, передайте вновь созданный объект (с одной ссылкой) в выделенный стеком объект CReferenceManager. Пример:

CReferenceManager<Test1> testRef(new Test1());

Чтобы передать объект в качестве аргумента другой функции или методу, всегда передавайте объект CReferenceManager по значению (не по ссылке и не по указателю). Если вы сделаете это таким образом, конструктор копирования и деструктор позаботятся о поддержании подсчета ссылок для вас. Пример:

void someFunction(CReferenceManager<Test1> testObj)
{
// use testObj as if it was a naked pointer
// reference mananagement is automatically handled
printf("some value: %d\n", testObj->someValue());
}

int main()
{
CReferenceManager<Test1> testRef(new Test1());
someFunction(testRef);
}

Если вам нужно вставить объект с подсчетом ссылок в контейнер, то вставьте обертку CReferenceManager по значению (не его указатель и не пустой указатель объекта). Пример:

std::vector< CReferenceManager<Test1> > myVector;
CReferenceManager<Test1> testRef(new Test1());
myVector.push_back(testRef);
myVector[0]->some_method(); // invoke the object as if it was a pointer!

Я полагаю, что если вы строго будете следовать вышеприведенным правилам, единственные проблемы, которые вы найдете, это ошибки в вашей реализации подсчета ссылок.

Пример реализации, которая следует этим правилам, находится в эта страница, хотя этому решению не хватает поддержки многопоточной защиты.

Надеюсь, это поможет!

5

Есть какой-то способ сделать это, но сначала позвольте мне спросить вас об одном. Почему вы хотите управлять ссылками вручную и предоставлять возможность утечек памяти? Вы можете легко использовать boost::intrusive_ptr сделать работу за вас? (если вы не хотите повышения, нет проблем, см. реализацию intrusive_ptr и реализовать свой собственный класс или просто скопировать его в свой собственный файл) и тогда у вас нет утечки памяти, чтобы найти его !!

Но в качестве ответа на ваш вопрос вы могли бы иметь 2 AddRef/Release один для отладочной версии и другой для выпуска, и вы должны добавить AddRef позиции в структуре, как std::stack и на Release вытолкнуть их из stack и в самом конце вы видите, сколько ссылок с позиций ведьм осталось в стеке! но если это для реализации COM, помните, что COM может вызвать AddRef несколько раз, а затем удалить их в более позднее время, и, таким образом, вы не можете понять, какие AddRef не соответствует Release,

3

Для проектов, в которых я участвую, у меня были похожие потребности. У нас есть собственный класс шаблонов смарт-указателей, и время от времени утечки памяти возникали из-за циклических ссылок.

Чтобы узнать, какой смарт-указатель, ссылающийся на просочившийся объект, все еще жив (2 или более), мы компилируем источники со специальным определением препроцессора, который включает специальный код отладки в реализации смарт-указателя. Вы можете взглянуть на наши класс умного указателя.

По сути, каждый смарт-указатель и объект с подсчетом ссылок получают уникальный идентификатор. Когда мы получаем идентификатор для просочившегося объекта (обычно используя valgrind для определения исходного местоположения выделения памяти для просочившегося объекта), мы используем наш специальный код отладки, чтобы получить все идентификаторы смарт-указателей, которые ссылаются на объект. Затем мы используем файл конфигурации, в котором записываем идентификаторы смарт-указателей, и при следующем запуске приложения этот файл читается нашим инструментом отладки, который затем знает, для какого нового экземпляра смарт-указателя он должен вызвать сигнал для входа в отладчик. Это показывает трассировку стека, где был создан этот экземпляр смарт-указателя.

По общему признанию, это включает в себя некоторую работу и может окупиться только для более крупных проектов.

Другой возможностью будет запись трассировки стека внутри вашего AddRef метод во время выполнения. Взгляни на мой ctkBackTrace класс для создания трассировки стека во время выполнения. Должно быть легко заменить определенные типы Qt стандартными типами STL.

2

Я думаю, что с небольшим количеством работы и использования libunwind Вы могли бы, вероятно, попытаться получить то, что вам нужно (что было бы очень важно).

http://www.nongnu.org/libunwind/docs.html

1

Принцип подсчета ссылок заключается в том, чтобы увеличить счетчик при ссылке пользователя на объект и уменьшить при разрыве ссылки.

Итак, вы должны:

  • манипулировать умными указателями, а не указателями, чтобы увеличить / уменьшить прозрачность
  • перегрузить конструктор копирования и назначить оператор smart_pointer

Символический пример:

  • A a = new A(); refcount = 0, никто не использует его
  • Link<A> lnk( a ); refcount = 1
  • obj.f( lnk ); obj store lnk, refcount = 2
  • этот метод может вернуться, так как право собственности было передано obj

Итак, взгляните на передачу параметров (возможно автоматическое копирование) и копирование в чужие объекты.

Хорошие учебники по этому вопросу существуют в CORBA туманности.

Вы также можете увидеть ACE или же ICE, или же 0MQ.

1

Один из способов сделать то, что вы просили, это передать AddRef и выпустить эту информацию, используя что-то вроде этого:

void CReferenceCount::AddRef(const char *file=0, int line=-1) { if (file) cout << "FILE:" << file; if (line>0) count << " LINE: " << line;   .... do the rest here ... }

Затем, когда вы вызываете функцию, вы можете использовать макрос, подобный тому, что предлагал Ролли выше, например:

#define ADDREF(x) x.AddRef(__FILE__, __LINE__)

Это передаст файл и строку, где сделан вызов, который, я полагаю, является тем, что вы просили. Вы можете контролировать, что вы хотите сделать с информацией в методах. Распечатка их, как я делал выше, является лишь примером. Вы можете захотеть собрать больше информации помимо этого и записать ее в другой объект, чтобы у вас была история ваших звонков, записать их в файл журнала и т. Д. Вы также можете передавать больше информации из точек вызова, чем просто файл и линии, в зависимости от типа и уровня отслеживания вам нужно. Параметры по умолчанию также позволяют вам использовать их, ничего не передавая (путем простого переопределения макросов), просто чтобы посмотреть, как будет вести себя окончательная версия, с накладными расходами на два нажатия стека и две проверки условий.

1

Краткий ответ: вы должны использовать идеи, которые опубликовали другие, а именно использовать макросы ADD / RELEASE и передавать предопределенные макросы __FILE__ и __LINE__, которые компилятор предоставляет вашему классу отслеживания.

Немного более длинный ответ: Вы также можете использовать функциональность, которая позволяет обходить стек и видеть, кто вызвал функцию, которая несколько более гибкая и чистая, чем при использовании макросов, но почти наверняка медленнее.

На этой странице показано, как этого добиться при использовании GCC: http://tombarta.wordpress.com/2008/08/01/c-stack-traces-with-gcc/.

В Windows вы можете использовать некоторые встроенные функции компилятора вместе с функциональностью поиска символов. Для деталей проверьте: http://www.codeproject.com/tools/minidump.asp

Обратите внимание, что в обоих случаях вашей программе нужно будет включить хотя бы несколько символов, чтобы это работало.

Если у вас нет особых требований для выполнения этого во время выполнения, я бы посоветовал вам ознакомиться с кратким ответом.

1
По вопросам рекламы [email protected]