Пример:
bool isHeapPtr(void* ptr)
{
//...
}
int iStack = 35;
int *ptrStack = &iStack;
bool isHeapPointer1 = isHeapPtr(ptrStack); // Should be false
bool isHeapPointer2 = isHeapPtr(new int(5)); // Should be true
/* I know... it is a memory leak */
Если у меня есть в классе членский указатель, и я не знаю, ново ли указывающий объект выделяется. Затем я должен использовать такую утилиту, чтобы знать, имею ли я к delete
указатель.
Но:
Мой дизайн еще не сделан. Так, я буду программировать его тот способ, к которому я всегда имею delete
это. Я собираюсь избежать мусорного программирования
Нет никакого способа сделать это - и если вам нужно это сделать, то с вашим дизайном что-то не так. Обсуждение того, почему вы не можете этого сделать, есть в Более эффективный C++.
Как вы могли не знать, выделено что-то в куче или нет? Вы должны спроектировать программное обеспечение так, чтобы оно имело единую точку распределения.
Если вы не делаете действительно экзотические вещи во встроенном устройстве или не работаете глубоко в собственном ядре, я просто не вижу в этом необходимости.
Посмотрите на этот код (без проверки ошибок, для примера):
class A
{
int *mysweetptr;
A()
{
mysweetptr = 0; //always 0 when unalloc'd
}
void doit()
{
if( ! mysweetptr)
{
mysweetptr = new int; //now has non-null value
}
}
void undoit()
{
if(mysweetptr)
{
delete mysweetptr;
mysweetptr = 0; //notice that we reset it to 0.
}
}
bool doihaveit()
{
if(mysweetptr)
return true;
else
return false;
}
~A()
{
undoit();
}
};
В частности, обратите внимание, что я использую нулевое значение, чтобы определить, был ли указатель выделен или нет, или мне нужно удалить это или нет.
Несмотря на громкие заявления об обратном, очевидно, что можно делать то, что вы хотите, платформенно-зависимым способом. Однако то, что что-то возможно, автоматически не делает это хорошей идеей. Простое правило stack == no delete, иначе == delete вряд ли сработает.
Более распространенный способ - сказать, что если я выделил буфер, то я должен его удалить. Если программа передает мне буфер, я не отвечаю за его удаление.
например.
class CSomething
{
public:
CSomething()
: m_pBuffer(new char[128])
, m_bDeleteBuffer(true)
{
}
CSomething(const char *pBuffer)
: m_pBuffer(pBuffer)
, m_bDeleteBuffer(false)
{
}
~CSomething()
{
if (m_bDeleteBuffer)
delete [] m_pBuffer;
}
private:
const char *m_pBuffer;
bool m_bDeleteBuffer;
};
В общем случае, боюсь, вам не повезло - поскольку указатели могут иметь любое значение, нет способа их отличить. Если бы вы знали адрес начала и размер вашего стека (например, из TCB во встроенной операционной системе), вы могли бы это сделать. Something like:
stackBase = myTCB->stackBase;
stackSize = myTCB->stackSize;
if ((ptrStack < stackBase) && (ptrStack > (stackBase - stackSize)))
isStackPointer1 = TRUE;
Единственное «хорошее» решение, которое я могу придумать, - это перегрузить оператор new
для этого класса и отслеживать его. Что-то вроде этого (код, скомпилированный мозгом):
class T {
public:
void *operator new(size_t n) {
void *p = ::operator new(n);
heap_track().insert(p);
return p;
}
void operator delete(void* p) {
heap_track().erase(p);
::operator delete(p);
}
private:
// a function to avoid static initialization order fiasco
static std::set<void*>& heap_track() {
static std::set<void*> s_;
return s_;
}
public:
static bool is_heap(void *p) {
return heap_track().find(p) != heap_track().end();
}
};
Затем вы можете делать что-то вроде этого:
T *x = new X;
if(T::is_heap(x)) {
delete x;
}
Однако я бы посоветовал не использовать дизайн, который требует, чтобы вы могли спрашивать, было ли что-то размещено в куче.
Единственный известный мне способ сделать это полунадежно - это перегрузить оператор new
для типа, для которого вам нужно это сделать. К сожалению, здесь есть несколько серьезных подводных камней, и я не могу вспомнить, какие именно.
Я знаю, что один из подводных камней заключается в том, что что-то может находиться в куче, не будучи выделенным напрямую. Например:
class A {
int data;
};
class B {
public:
A *giveMeAnA() { return &anA; }
int data;
A anA;
};
void foo()
{
B *b = new B;
A *a = b->giveMeAnA();
}
В приведенном выше коде a
в foo
заканчивается указателем на объект на куче, который не был выделен с помощью new
. Если ваш вопрос действительно "Как узнать, могу ли я вызвать delete
для этого указателя.", то перегрузка operator new
для выполнения чего-то хитрого может помочь вам ответить на этот вопрос. Я все еще думаю, что если вам приходится задавать этот вопрос, то вы сделали что-то очень неправильное.
Вы пытаетесь сделать это сложным путем. Уточните свой дизайн, чтобы было ясно, кто "владеет" данными, и позвольте коду разобраться с их сроком службы.
В основных операционных системах стек растет сверху, а куча - снизу. Таким образом, вы можете эвристически проверить, не выходит ли адрес за пределы большого значения, для некоторого определения «большого». Например, в моей 64-битной системе Linux работает следующее:
#include <iostream>
bool isHeapPtr(const void* ptr) {
return reinterpret_cast<unsigned long long int>(ptr) < 0xffffffffull;
}
int main() {
int iStack = 35;
int *ptrStack = &iStack;
std::cout << isHeapPtr(ptrStack) << std::endl;
std::cout << isHeapPtr(new int(5)) << std::endl;
}
Обратите внимание, что это грубая эвристика, с которой может быть интересно поиграть, но она не подходит для производственного кода.
Ваш дизайн не должен полагаться на определение этой информации (как указывали другие, на самом деле это невозможно). Вместо этого ваш класс должен явно определять владение указателями, которые он принимает в своем конструкторе или методах. Если ваш класс становится владельцем этих указателей, то передача указателя на стек или глобальный объект является неправильным, и вы должны удалить его, зная, что неправильный клиентский код может привести к сбою. Если ваш класс не становится владельцем, он не должен удалять указатель.
вот универсальный способ сделать это в windows с помощью TIP:
bool isStack(void* x)
{
void* btn, *top;
_asm {
mov eax, FS:[0x08]
mov btn, eax
mov eax, FS:[0x04]
mov top, eax
}
return x < top && x > btn;
}
void func()
{
int i;
bool b1;
bool b2;
b1 = isStack(&i);
b2 = isStack(&si);
return;
}
Во-первых, зачем вам это знать? Какую реальную проблему вы пытаетесь решить?
Я знаю, что единственный способ сделать такое определение - это перегрузить глобальный оператор new
и оператор delete
. Затем вы можете спросить свой диспетчер памяти, принадлежит ли ему указатель (куча) или нет (стек или глобальные данные).
вот оно, работает в MSVC:
#define isheap(x, res) { \
void* vesp, *vebp; \
_asm {mov vesp, esp}; \
_asm {mov vebp, ebp}; \
res = !(x < vebp && x >= vesp); }
int si;
void func()
{
int i;
bool b1;
bool b2;
isheap(&i, b1);
isheap(&si, b2);
return;
}
немного уродливо, но работает. Работает только для локальных переменных. Если вы передадите указатель стека из вызывающей функции, этот макрос вернет true (значит, это куча)
Итак, возьмите свою книгу ассемблера и сравните адрес вашего указателя с указателем стека:
int64_t x = 0;
asm("movq %%rsp, %0;" : "=r" (x) );
if ( myPtr < x ) {
...in heap...
}
Теперь x будет содержать адрес, с которым вам нужно будет сравнить ваш указатель. Обратите внимание, что он не будет работать с памятью, выделенной в другом потоке, поскольку у него будет свой собственный стек.
Даже если вы можете определить, находится ли указатель в одной конкретной куче или одном конкретном стеке, для одного приложения может быть несколько куч и несколько стеков.
Исходя из причины вопроса, для каждого контейнера чрезвычайно важно иметь строгую политику в отношении того, «владеет» ли он указателями, которые он содержит, или нет. В конце концов, даже если эти указатели указывают на память, выделенную кучей, какой-то другой фрагмент кода также может иметь копию того же указателя. У каждого указателя должен быть один «владелец» за раз, хотя право владения может передаваться. Владелец несет ответственность за разрушение.
В редких случаях для контейнера полезно отслеживать как принадлежащие, так и чужие указатели - либо с помощью флагов, либо путем их хранения отдельно. Однако в большинстве случаев проще установить четкую политику для любого объекта, который может содержать указатели. Например, у большинства интеллектуальных указателей всегда есть свои настоящие указатели-контейнеры.
Конечно, здесь важны интеллектуальные указатели - если вам нужен указатель, отслеживающий владение, я уверен, что вы можете найти или написать тип интеллектуального указателя, чтобы абстрагироваться от этих хлопот.