Как знать, указывает ли указатель на "кучу" или стек?

Пример:

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 это. Я собираюсь избежать мусорного программирования

27
задан jww 5 October 2014 в 02:31
поделиться

14 ответов

Нет никакого способа сделать это - и если вам нужно это сделать, то с вашим дизайном что-то не так. Обсуждение того, почему вы не можете этого сделать, есть в Более эффективный C++.

30
ответ дан 28 November 2019 в 04:16
поделиться

Как вы могли не знать, выделено что-то в куче или нет? Вы должны спроектировать программное обеспечение так, чтобы оно имело единую точку распределения.

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

Посмотрите на этот код (без проверки ошибок, для примера):

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();
}
};

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

0
ответ дан 28 November 2019 в 04:16
поделиться

Несмотря на громкие заявления об обратном, очевидно, что можно делать то, что вы хотите, платформенно-зависимым способом. Однако то, что что-то возможно, автоматически не делает это хорошей идеей. Простое правило 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;
};
2
ответ дан 28 November 2019 в 04:16
поделиться

В общем случае, боюсь, вам не повезло - поскольку указатели могут иметь любое значение, нет способа их отличить. Если бы вы знали адрес начала и размер вашего стека (например, из TCB во встроенной операционной системе), вы могли бы это сделать. Something like:

stackBase = myTCB->stackBase;
stackSize = myTCB->stackSize;

if ((ptrStack < stackBase) && (ptrStack > (stackBase - stackSize)))
    isStackPointer1 = TRUE;
15
ответ дан 28 November 2019 в 04:16
поделиться

Единственное «хорошее» решение, которое я могу придумать, - это перегрузить оператор 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;
}

Однако я бы посоветовал не использовать дизайн, который требует, чтобы вы могли спрашивать, было ли что-то размещено в куче.

12
ответ дан 28 November 2019 в 04:16
поделиться

Единственный известный мне способ сделать это полунадежно - это перегрузить оператор 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 для выполнения чего-то хитрого может помочь вам ответить на этот вопрос. Я все еще думаю, что если вам приходится задавать этот вопрос, то вы сделали что-то очень неправильное.

0
ответ дан 28 November 2019 в 04:16
поделиться

Вы пытаетесь сделать это сложным путем. Уточните свой дизайн, чтобы было ясно, кто "владеет" данными, и позвольте коду разобраться с их сроком службы.

2
ответ дан 28 November 2019 в 04:16
поделиться

В основных операционных системах стек растет сверху, а куча - снизу. Таким образом, вы можете эвристически проверить, не выходит ли адрес за пределы большого значения, для некоторого определения «большого». Например, в моей 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;
}

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

3
ответ дан 28 November 2019 в 04:16
поделиться

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

0
ответ дан 28 November 2019 в 04:16
поделиться

вот универсальный способ сделать это в 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;
}
1
ответ дан 28 November 2019 в 04:16
поделиться

Во-первых, зачем вам это знать? Какую реальную проблему вы пытаетесь решить?

Я знаю, что единственный способ сделать такое определение - это перегрузить глобальный оператор new и оператор delete . Затем вы можете спросить свой диспетчер памяти, принадлежит ли ему указатель (куча) или нет (стек или глобальные данные).

3
ответ дан 28 November 2019 в 04:16
поделиться

вот оно, работает в 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 (значит, это куча)

5
ответ дан 28 November 2019 в 04:16
поделиться

Итак, возьмите свою книгу ассемблера и сравните адрес вашего указателя с указателем стека:

int64_t x = 0;
asm("movq %%rsp, %0;" : "=r" (x) );
if ( myPtr < x ) {
   ...in heap...
}

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

8
ответ дан 28 November 2019 в 04:16
поделиться

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

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

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

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

3
ответ дан 28 November 2019 в 04:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: