Подтвердить что ты не робот

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

У меня

class Foo {
....
}

Есть ли способ, чтобы Foo мог выделить:

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

и

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

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

Edit:

Alof людей спросил меня "зачем это делать?"

Ответ:

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

4b9b3361

Ответ 1

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

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

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


Update:

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

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

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

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

Ответ 4

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

Ответ 5

Возможно, если вы сравните значение 'this' с текущим значением указателя стека. Если это & ​​lt; 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

Ответ 6

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

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

Ответ 7

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

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

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

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

Ответ 8

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

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

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

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

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

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

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

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

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

Ответ 9

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

Ответ 10

Мета-вопрос, заданный pax, задан "зачем вы хотите это сделать", вы, скорее всего, получите более информативный ответ.

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

new, delete, new no throw, delete no throw, new array, delete array, new array no throw, delete array no throw, размещение нового, удаление места размещения, размещение нового массива, размещение массива delete.

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

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

Ответ 11

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

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

Ответ 12

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

Ответ 13

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

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

Ответ 14

Взгляните на программу здесь: 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