Может ли класс C ++ определить, находится ли он в стеке или в куче? - PullRequest
35 голосов
/ 13 января 2010

У меня есть

class Foo {
....
}

Есть ли способ у Foo выделить:

function blah() {
  Foo foo; // on the stack
}

и

function blah() {
  Foo foo* = new Foo(); // on the heap
}

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

Редактировать:

Многие люди спрашивали меня, "зачем это делать?"Ответ:

Я сейчас использую GC с пересчетом.Тем не менее, я хочу иметь возможность бегать и метать тоже.Для этого мне нужно пометить набор «корневых» указателей - это указатели в стеке.Таким образом, для каждого класса я хотел бы знать, находятся ли они в стеке или в куче.

Ответы [ 15 ]

14 голосов
/ 13 января 2010

Хакерский способ сделать это:

struct Detect {
   Detect() {
      int i;
      check(&i);
   }

private:
   void check(int *i) {
      int j;
      if ((i < &j) == ((void*)this < (void*)&j))
         std::cout << "Stack" << std::endl;
      else
         std::cout << "Heap" << std::endl;
   }
};

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

(наверняка есть системы, где это не сработает)

10 голосов
/ 13 января 2010

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

Почему вы думаете, что вам нужно это сделать?

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


Обновление:

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

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

Вы думали о том, как вы собираетесь с этим справиться?

Классический пример:

myobject *x = new xclass();
x = 0;

не приведет к вызову удаления.

Кроме того, как вы обнаружите тот факт, что указатель на один из ваших экземпляров находится в стеке? Перехват new и delete может позволить вам сохранить, является ли сам объект основанным на стеке или куче, но я не знаю, как вы скажете, куда будет назначен указатель, особенно с таким кодом:

myobject *x1 = new xclass();  // yes, calls new.
myobject *x2 = x;             // no, it doesn't.
8 голосов
/ 13 января 2010

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

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

4 голосов
/ 13 января 2010

Это возможно, если вы сравните значение this с текущим значением указателя стека. Если это

Попробуйте это (используя gcc в x86-64):

#include <iostream>

class A
{
public:
    A()
    {
        int x;

        asm("movq %1, %%rax;"
            "cmpq %%rsp, %%rax;"
            "jbe Heap;"
            "movl $1,%0;"
            "jmp Done;"
            "Heap:"
            "movl $0,%0;"
            "Done:"
            : "=r" (x)
            : "r" (this)
            );

        std::cout << ( x ? " Stack " : " Heap " )  << std::endl; 
    }
};

class B
{
private:
    A a;
};

int main()
{
    A a;
    A *b = new A;
    A c;
    B x;
    B *y = new B;
    return 0;
}

Должен вывести:

* +1007 *
4 голосов
/ 13 января 2010

Более прямым и менее навязчивым методом будет поиск указателя на картах областей памяти (например, /proc/<pid>/maps). Каждый поток имеет область, выделенную для его стека. Статические и глобальные переменные будут находиться в разделе .bss , константы в сегменте rodata или const и т. Д.

3 голосов
/ 13 января 2010

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

char buf[0xff]; (Foo*)buf;

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

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

3 голосов
/ 13 января 2010

Я не уверен, что вы спрашиваете, но переопределение оператора new может быть тем, что вы пытаетесь сделать. Поскольку единственный безопасный способ создания объекта в куче в C ++ - это использование оператора new, вы можете различать объекты, которые существуют в куче, и другие формы памяти. Google "перегрузка нового в c ++" для получения дополнительной информации.

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

2 голосов
/ 22 апреля 2012

Способ для классов MFC:

.h

class CTestNEW : public CObject
{
public:
    bool m_bHasToBeDeleted;
    __declspec(thread) static void* m_lastAllocated;
public:
#ifdef _DEBUG
    static void* operator new(size_t size, LPCSTR file, int line) { return internalNew(size, file, line); }
    static void operator delete(void* pData, LPCSTR file, int line) { internalDelete(pData, file, line); }
#else
    static void* operator new(size_t size) { return internalNew(size); }
    static void operator delete(void* pData) { internalDelete(pData); }
#endif
public:
    CTestNEW();
public:
#ifdef _DEBUG
    static void* internalNew(size_t size, LPCSTR file, int line)
    {
        CTestNEW* ret = (CTestNEW*)::operator new(size, file, line);
        m_lastAllocated = ret;
        return ret;
    }

    static void internalDelete(void* pData, LPCSTR file, int line)
    {
        ::operator delete(pData, file, line);
    }
#else
    static void* internalNew(size_t size)
    {
        CTestNEW* ret = (CTestNEW*)::operator new(size);
        return ret;
    }

    static void internalDelete(void* pData)
    {
        ::operator delete(pData);
    }
#endif
};

.CPP

#include "stdafx.h"
.
.
.
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

void* CTestNEW::m_lastAllocated = NULL;
CTestNEW::CTestNEW()
{
    m_bHasToBeDeleted = (this == m_lastAllocated);
    m_lastAllocated = NULL;
}
2 голосов
/ 13 января 2010

Нет, это невозможно сделать надежно или разумно.

Возможно, вы сможете определить, когда объект выделен с помощью new, перегрузив new.

Но что тогда, если объект создается как член класса, а класс-владелец размещается в куче?

Вот третий пример кода, который нужно добавить к двум имеющимся у вас:

class blah {
  Foo foo; // on the stack? Heap? Depends on where the 'blah' is allocated.
};

А как насчет статических / глобальных объектов? Как бы вы их отличали от стековых / кучных?

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

Так что, действительно, лучший ответ заключается в том, что «есть причина , почему GC mark & ​​sweep не используются с C ++». Если вам нужен правильный сборщик мусора, используйте другой язык, который его поддерживает.

С другой стороны, большинство опытных программистов на C ++ считают, что требуется для сборщика мусора, в значительной степени исчезает, когда вы изучаете необходимые методы управления ресурсами ( RAII ).

1 голос
/ 27 марта 2013

Чтобы ответить на ваш вопрос, надежный способ (при условии, что ваше приложение не использует более одного потока), при условии, что все, что не содержится в вашем умном указателе, не в куче:

-> Перегрузка нового, так что вы можете сохранить список всех выделенных блоков с размером каждого блока. -> Когда конструктор вашего умного указателя, поиск в котором этот указатель принадлежат. Если его нет ни в одном блоке, вы можете сказать, что он «в стеке» (на самом деле это означает, что он не управляется вами). В противном случае вы знаете, где и когда был выделен ваш указатель (если вы не хотите искать потерянные указатели и свободную память или что-то в этом роде ...) Это не зависит от архитектуры.

...