Может ли класс C ++ определить, в стеке он или в куче?

у меня есть

class Foo {
....
}

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

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

и

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

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

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

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

Ответ:

Сейчас я использую сборщик мусора с подсчетом ссылок. Однако я также хочу иметь возможность запускать mark & ​​sweep. Для этого мне нужно пометить набор «корневых» указателей - это указатели в стеке. Таким образом, для каждого класса я хотел бы знать, находятся ли они в стеке или в куче.


person anon    schedule 13.01.2010    source источник
comment
Отделить, как в? Статические выделения IMHO выполняются в стеке, а выделения, такие как «новые», будут выполняться в куче.   -  person    schedule 13.01.2010
comment
Что вы имеете в виду, отделить? Вы спрашиваете, есть ли способ для объекта Foo определить, был ли он создан в стеке или в куче?   -  person Asher Dunn    schedule 13.01.2010
comment
хм .. так вы хотите узнать, где находится 'foo'?   -  person    schedule 13.01.2010
comment
Зачем вам нужно различать их, каков вариант использования?   -  person Georg Fritzsche    schedule 13.01.2010
comment
можно просто ответить на вопрос? независимо от того, знает ли парень, что он делает, это может быть полезно для тех из нас, кто действительно в этом нуждается.   -  person Matt Joiner    schedule 13.01.2010
comment
Закатывает глазами в ошеломленном замешательстве :-(   -  person Martin York    schedule 13.01.2010
comment
Мне кажется, что anon пытается воспроизвести поведение boost::invasive_ptr, но внутри самого класса. Мне кажется, что это много неприятностей, но без всякой пользы.   -  person greyfade    schedule 13.01.2010
comment
@Anacrolix; Это невозможно сделать портативно, он не скажет вам ничего полезного, если бы это могло быть, и если вы думаете, что вам это нужно, вы почти наверняка ошибаетесь.   -  person JoeG    schedule 13.01.2010
comment
Настоящая суть, отметка и развертка, приятна, но: 1. Это означает, что все работает с ней хорошо, то есть все ваши зависимости, вплоть до STL включительно, 2. Есть эта пауза, которую мир влияет, когда он срабатывает, хотя я уже видели исследование Eiffel, в котором во избежание этого используются точки, в котором были получены довольно интересные результаты, хотя для этого требуется специальный поток для GC.   -  person Matthieu M.    schedule 13.01.2010
comment
Это было действительно полезно для меня, потому что я делаю нечто подобное. +1   -  person Patrick Niedzielski    schedule 24.11.2010
comment
Предположить на основе значения указателя стека (полученного, конечно, из непереносимой встроенной сборки), а затем сделать разумное предположение о размере стека? А это не работает и как помогает?   -  person Robert Mason    schedule 07.06.2012


Ответы (15)


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

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


Обновление:

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

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

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

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

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

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

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

myobject *x1 = new xclass();  // yes, calls new.
myobject *x2 = x;             // no, it doesn't.

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

Возможно, в вашем сценарии ручная сборка мусора больше не требуется.


(a) Это известно как X/Y problem. Часто люди задают вопрос, предполагающий класс решения, тогда как лучший подход - просто описать проблему без никаких предубеждений относительно того, какое будет лучшее решение.

person paxdiablo    schedule 13.01.2010
comment
Я ожидал, что в сборщике мусора, предназначенном для маркировки / зачистки пользователя, предоставляется какой-то интеллектуальный указатель, содержащий указатели на собираемые объекты (по сути, это обеспечивает точную маркировку). Таким образом, ваши фрагменты кода не являются законными, поскольку они ссылаются на объект gc, используя только необработанный указатель, отличный от gc. Реализация на уровне компилятора может использовать консервативную разметку и напрямую анализировать стек. - person Steve Jessop; 13.01.2010
comment
Перегрузка новой не совсем надежна. Вы можете malloc () создать буфер и разместить новый (или просто привести) его к классу. Это все равно выглядело бы как класс на основе стека, но он находится в куче. ИМО, вы не можете собирать мусор, созданный с помощью new: вам понадобятся ваши собственные обертки выделения и указателя. - person AshleysBrain; 13.01.2010
comment
Я планирую использовать это вместе с умными указателями с подсчетом ссылок. У них есть перегрузка создания, оператора = и деструктора. Приведенный выше пример будет выглядеть так: MyObject :: Ptr x = new MyObject (); х = 0; // перегрузка operator = заставляет x выполнять декремент ref, который запускает деструктор. - person anon; 13.01.2010
comment
Вам следует попробовать boost::shared_ptr, для более канонической и проверенной реализации подсчета ссылок. - person GManNickG; 13.01.2010
comment
@GManNickG или, в C ++ 11, std::shared_ptr, который исправил некоторые проблемы с boost::shared_ptr. - person ; 22.04.2012

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

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

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

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

person sth    schedule 13.01.2010
comment
И не то чтобы я рекомендовал делать это для каких-либо реальных задач, просто забавная идея, которая пришла в голову. - person sth; 13.01.2010
comment
Я не тестировал это, но это может не работать в многопоточном приложении. - person Nick Dandoulakis; 13.01.2010
comment
Да, я знаю, что ты знаешь эти вещи, просто почувствовал, что это нужно сказать. :) - person GManNickG; 13.01.2010
comment
Я также уверен, что он знал, что вы знали, что он знал и только что говорил. - person Martin York; 13.01.2010
comment
Все они убивают себя через 100 дней: en.wikipedia.org/wiki/Common_knowledge_%28logic% 29 - person Steve Jessop; 13.01.2010
comment
Я действительно пробовал это примерно в 2003 году. К сожалению, одна из систем, на которой он не работает, - это практически любой компилятор C ++ с включенной оптимизацией. - person Daniel Earwicker; 20.02.2010
comment
Это не будет работать в любой современной системе, т.е. любой системе, которая поддерживает потоки. - person Paul Groke; 21.04.2015
comment
@NickDandoulakis, почему потоки ломают это? может что-то мне не хватает. - person thang; 12.10.2018
comment
@thang, обычно стек каждого потока также размещается где-то в куче, поэтому предположение, что куча обычно увеличивается с другой стороны, может быть неверным. - person Nick Dandoulakis; 12.10.2018
comment
@NickDandoulakis, это хороший аргумент. Думаю, мы сможем исправить этот хак с помощью другого хака! Если & i находится рядом с указателем this, значит, мы находимся в стеке. в противном случае - куча. я думаю, это сработало бы лучше. здесь предполагается, что конструктор вызывается сразу после выделения this. - person thang; 14.10.2018
comment
@thang, возможно, рядом может работать, если он зависит от класса? Т.е. мы принимаем во внимание определение класса, скрытые поля, такие как vtable, и встраиваем код в верхнюю часть конструктора. Но можем ли мы это обобщить? - person Nick Dandoulakis; 14.10.2018
comment
@NickDandoulakis, я думаю, вам просто нужно проверить, что это примерно sizeof (* this), плюс-минус несколько байтов из настроенного фрейма стека. Указатель на vtable будет включен в sizeof. - person thang; 15.10.2018
comment
@thang, размер кадра стека сложно. Он может сильно различаться в зависимости от реализации, размера аргументов конструктора и от того, вызывается ли конструктор из подкласса. - person Nick Dandoulakis; 15.10.2018
comment
@NickDandoulakis, все верно, но было бы сложно, если он будет таким большим, чтобы перепутать стек с кучей. - person thang; 16.10.2018

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

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

person Terry Mahaffey    schedule 13.01.2010
comment
Является ли new взлом ненадежным: как узнать, будет ли вызвано новое размещение, помещать объект в стек или в кучу? - person Matthieu M.; 13.01.2010
comment
Вопрос в том, как это сделать, а не в том, как это сделать стандартно / портативно. - person Justicle; 12.02.2010

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

Попробуйте это (используя 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;
}

Он должен выводить:

Stack 
Heap 
Stack 
Stack 
Heap
person Gianni    schedule 13.01.2010
comment
Не могли бы вы повторно ввести эту часть asm () для VC ++? У меня проблемы с использованием под VS2008. Спасибо. - person Aoi Karasu; 31.05.2010

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

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

person Michael Koval    schedule 13.01.2010
comment
Не обязательно верно. Рассмотрим вектор этих объектов. Данные для вектора могли быть выделены из кучи, но объект никогда не вызывал новых вызовов. - person GManNickG; 13.01.2010
comment
Построение объектов в векторе вызывает размещение нового для создания объекта. Теперь я не уверен, означает ли это, что вам также нужно предоставить новое место для размещения или нет ... раньше мне не приходилось копать так глубоко. - person Michael Anderson; 13.01.2010
comment
Место размещения-new не подлежит замене. Тем не менее, вектор не использует размещение-new. (Или контейнеры, если на то пошло.) Они вызывают construct метод своего распределителя. (Обычно это называется размещение-new.: P) - person GManNickG; 13.01.2010
comment
Хорошее замечание о векторах, хотя я думаю, вы имеете в виду массивы? Выделение в массиве можно запретить, сделав конструктор по умолчанию закрытым, но это некрасиво, особенно если объекту в противном случае не нужны параметры в своем конструкторе. - person Dan Breslau; 13.01.2010

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

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

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

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

person Inso Reiges    schedule 13.01.2010

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

person Matt Joiner    schedule 13.01.2010

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

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

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

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

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

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

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

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

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

person jalf    schedule 13.01.2010

Способ для классов 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;
}
person ggo    schedule 22.04.2012

Перегрузите new () для вашего класса. Таким образом, вы сможете различать распределение кучи и стека, но не между стеком и статическим / глобальным.

person Seva Alekseyev    schedule 13.01.2010
comment
Это также приносит большую головную боль, когда экземпляр вашего класса является нестатическим членом другого класса. - person ; 22.04.2012

На мета-вопрос, заданный pax, задается вопрос «зачем вам это нужно?», Вы, вероятно, получите более информативный ответ.

Теперь, предполагая, что вы делаете это «по уважительной причине» (возможно, просто из любопытства), можно добиться такого поведения, переопределив операторы new и delete, но не забудьте переопределить все 12 вариантов, включая:

новое, удаление, новое без выброса, удаление без выброса, новый массив, удаление массива, новый массив без выброса, удаление массива без выброса, новое размещение, удаление размещения, размещение нового массива, удаление массива размещения.

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

Это своего рода боль, так какого другого поведения вы хотели?

person Rick    schedule 13.01.2010
comment
Есть одна проблема - размещение new можно использовать в памяти из стека и из кучи. Как это отличить? - person Tadeusz Kopec; 13.01.2010

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

перегрузка new и delete может привести к большему количеству дыр, чем вы можете себе представить.

person Arohi    schedule 16.02.2011

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

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

person Ano    schedule 26.03.2013
comment
Это правильная идея, но вам, возможно, придется побеспокоиться и о стандартных, и о новых распределителях. Если ваш класс содержит вектор, вам нужно знать, что его хранилище также отслеживается. Стандартные распределители используют :: operator new, так что вы можете просто переопределить это и готово. - person Charphacy; 28.04.2014

Решение есть, но оно вызывает наследование. См. Мейерс, Более эффективный C ++, пункт 27.

РЕДАКТИРОВАТЬ:
Предложение Мейерса кратко изложено в статье написано Роном ван дер Валем, на которое сам Майерс указал в своем блоге (в этом посте):

Отслеживание объектов на основе кучи

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

class HeapTracked {
  // Class-global list of allocated addresses
  typedef const void *RawAddress;
  static list<RawAddress> addresses;
public:
  // Nested exception class
  class MissingAddress {};

  // Virtual destructor to allow dynamic_cast<>; pure to make
  // class HeapTracked abstract.
  virtual ~HeapTracked()=0;

  // Overloaded operator new and delete
  static void *operator new(size_t sz)
  {
    void *ptr=::operator new(sz);
    addresses.push_front(ptr);
    return ptr;
  }

  static void operator delete(void *ptr)
  {
    // Remove ‘ptr’ from ‘addresses’
    list<RawAddress>::iterator it=find(addresses.begin(),

    addresses.end(), ptr);
    if (it !=addresses.end()) {
      addresses.erase(it);
      ::operator delete(ptr);
    } else
      throw MissingAddress();
  }

  // Heap check for specific object
  bool isOnHeap() const
  {
    // Use dynamic cast to get start of object block
    RawAddress ptr=dynamic_cast<RawAddress>(this);
    // See if it’s in ‘addresses’
    return find(addresses.begin(), addresses.end(), ptr) !=
      addresses.end();
  }
};

// Meyers omitted first HeapTracked:: qualifier...
list<HeapTracked::RawAddress> HeapTracked::addresses; 

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

person OfirD    schedule 15.06.2016

Взгляните на программу здесь: http://alumni.cs.ucr.edu/~saha/stuff/memaddr.html. С помощью нескольких приведений он выводит:

        Address of main: 0x401090
        Address of afunc: 0x401204
Stack Locations:
        Stack level 1: address of stack_var: 0x28ac34
        Stack level 2: address of stack_var: 0x28ac14
        Start of alloca()'ed array: 0x28ac20
        End of alloca()'ed array: 0x28ac3f
Data Locations:
        Address of data_var: 0x402000
BSS Locations:
        Address of bss_var: 0x403000
Heap Locations:
        Initial end of heap: 0x20050000
        New end of heap: 0x20050020
        Final end of heap: 0x20050010
person Ray Tayek    schedule 27.03.2013