Любой способ определить, является ли объект моего класса создан в стеке? - PullRequest
5 голосов
/ 28 октября 2019

Теперь мне нужно определить, создан ли мой класс как переменная stack / global / thread_local, например:

class Foo {
 public:
  Foo() {
    if(im_on_stack) {
      std::cout << "I'm on stack" << std::endl;
    } else if(im_in_global) {
      std::cout << "I'm in global" << std::endl;
    } else if(im_a_thread_local) {
      std::cout << "I'm a thread_local" << std::endl;
    } else {
      std::cout << "I'm on ohters location" << std::endl;
    }
  }
};

class Bar {
  Foo mFoo;
};

Foo gFoo;
thread_local Foo tFoo;
int main() {
  Foo lFoo;
}

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

I'm on ohters location
I'm in global
I'm a thread_local
I'm on stack

Это как-нибудь в C ++, я могу это сделать?

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

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

Edit2 :

В соответствии с концепцией gc root, которая является ссылкой, которой нет в куче, мне, вероятно, следует спросить следующим образом: могу ли я определить, создается ли мой класс накуча? Но я думаю, что в куче или в стеке этот вопрос не имеет значения.

Ответы [ 5 ]

2 голосов
/ 28 октября 2019

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

#include <iostream>
#include <memory>

struct A
{
    A()
    {
        int test = 0; // test is on the stack
        auto distance = reinterpret_cast<char*>(this) - reinterpret_cast<char*>(&test);
        isStack = std::abs(distance) < 1024; // completely arbitrary magic number, will need to experiment
    }

    bool isStack;
};

int main()
{
    std::cout << "stack: " << A().isStack << "\n";
    std::cout << "stack: " << std::make_unique<A>()->isStack << "\n";
}

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

1 голос
/ 28 октября 2019

Краткий ответ: Нет. Не в стандарте C ++. Могут быть решения для конкретных компиляторов или ОС, но ничего переносимого.

0 голосов
/ 28 октября 2019

Нет, но вы можете предотвратить создание объектов в куче / стеке.

  1. К предотвратить создание в стеке сделать деструктор частным / защищенным :

    class heap_only
    {
    public:
      void release() const { delete this; }
    protected: 
      ~heap_only() {}
    };
    
    struct heap_only_deleter
    {
      void operator()( heap_only* p ) { p->release(); }
    };
    
    using up_heap_only = std::unique_ptr<heap_only, heap_only_deleter>;
    
    //...
    heap_only ho; // fails
    
    heap_only* pho = new heap_only();
    pho->release();
    
    up_heap_only up{ new heap_only() };
    
  2. К предотвратить создание в куче сделать новых операторов частными / защищенными :

    class stack_only
    {
    protected:
      static void* operator new( std::size_t );
      static void* operator new[]( std::size_t );
    };
    
    //...
    stack_only* pso = new stack_only(); // fails
    
    stack_only so;
    

Относительно ваших правок и просто для удовольствия :

void* pstart;

bool is_on_stack( void* p )
{
  int end;
  return pstart >= p && p > &end;
}

int globalint;

int main()
{
  //
  int start;
  pstart = &start;

  //
  int stackint;
  std::cout << std::boolalpha 
    << is_on_stack( &globalint ) << std::endl
    << is_on_stack( &stackint ) << std::endl
    << is_on_stack( new int );
  //...
}
0 голосов
/ 28 октября 2019

То, что я написал в своем комментарии:

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

Демонстрация:

#include <iostream>
#include <set>

class Object {
  private:
    static thread_local std::set<void*> pNewSet;
    bool _isNewed;

  public:
    Object();
    Object(const Object&) = delete;
    const Object& operator=(const Object&) = delete;
    ~Object() = default;

    static void* operator new(size_t size);

    bool isNewed() const { return _isNewed; }

  private:
    static std::set<void*>& getNewPtrs()
    {
      static thread_local std::set<void*> pNewSet;
      return pNewSet;
    }
};

void* Object::operator new(size_t size)
{
  std::set<void*> &pNewSet = getNewPtrs();
  void *p = ::operator new(size);
  if (p) pNewSet.insert(p);
  return p;
}

Object::Object(): _isNewed(false)
{
  std::set<void*> &pNewSet = getNewPtrs();
  std::set<void*>::iterator iter = pNewSet.find((void*)this);
  if (iter != pNewSet.end()) {
    _isNewed = true;
    pNewSet.erase(iter);
  }
}

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__ 

// a global static instance
static Object o;

int main()
{
  DEBUG(std::cout << o.isNewed() << '\n');
  // a static instance (local scope)
  DEBUG(static Object o1);
  DEBUG(std::cout << o1.isNewed() << '\n');
  // a local instance
  DEBUG(Object o2);
  DEBUG(std::cout << o2.isNewed() << '\n');
  // as members
  DEBUG(struct Composed { Object o1, o2; } comp);
  DEBUG(std::cout << comp.o1.isNewed() << ' ' << comp.o2.isNewed() << '\n');
  // created with new
  DEBUG(Object *pO = new Object());
  DEBUG(std::cout << pO->isNewed() << '\n');
  DEBUG(delete pO);
  // created as members in an object created with new
  DEBUG(Composed *pComp = new Composed());
  DEBUG(std::cout << pComp->o1.isNewed() << ' ' << pComp->o2.isNewed() << '\n');
  DEBUG(delete pComp);
}

Вывод:

std::cout << o.isNewed() << '\n';
0
static Object o1;
std::cout << o1.isNewed() << '\n';
0
Object o2;
std::cout << o2.isNewed() << '\n';
0
struct Composed { Object o1, o2; } comp;
std::cout << comp.o1.isNewed() << ' ' << comp.o2.isNewed() << '\n';
0 0
Object *pO = new Object();
std::cout << pO->isNewed() << '\n';
1
delete pO;
Composed *pComp = new Composed();
std::cout << pComp->o1.isNewed() << ' ' << pComp->o2.isNewed() << '\n';
0 0
delete pComp;

Демонстрация в реальном времени в компиляторе Explorer

Примечания:

  1. Конструктор копирования и оператор присваивания Object удалены намеренно,Производные классы могут предоставлять конструктор копирования, но он должен вызывать Object::Object().

  2. Образец не учитывает другие варианты new (например, operator new[] или варианты выравнивания, посколькуС ++ 17). Это должно быть сделано в производительном коде.

0 голосов
/ 28 октября 2019

Это не является частью спецификации, поэтому нет.

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

...