Any way to detect whether an object of my class is create on stack?

Viewed 186

Now I need to detect whether my class is created as a stack/global/thread_local variable, for example:

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

and the out put should be:

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

This there any way in C++ I can do this?

Edit:

why I'm doing this: I'm writing a garbage collection library, and I got a class, let's call it gc_ptr, I need to know if this gc_ptr is a gc root (which is create on the location I mentioned) or not (which is a member of another class)

Edit2:

According to the concept of gc root, which is a reference which is not on a heap, I should probably asked in this way: can I detect if my class is create on the heap? But I think on heap or on stack make this question no difference.

5 Answers

Short answer: No. Not with standard C++. There may be compiler or OS specific solutions, but nothing portable.

I guess you could make a heuristic to detect stack allocated objects by detecting in their constructor whether their address is close to a stack allocated variable. Assuming that the stack and the heap have completely different memory addresses it should work. Completely undefined behaviour though according to the standard. e.g.:

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

I don't think you could expand this technique to thread local variables. You'd also need to take care in copy constructors and assignment operators to handle copying from a stack to a heap object and vice versa.

This is not part of the spec, so no.

If you told us on what system/os you are working, maybe we can assist - some systems may provide the stack address and size - usually this is available in embedded devices as part of the compiler output (the address), and as input (the size) as part of the environment/project configuration.

What I wrote in my comment:

Can be done to some extent: Assuming, you need it only for a certain kind of classes: You have to overload new (or wrap dynamic construction in a static create). You have to derive all considered classes from a base class with a specific constructor. To pass info from new to the constructor is a bit tricky. In our case, we used a global set where new remembered pointers to created instances and the corresponding constructor looked into it to determine whether creation was done by new. That was sufficient for us. (About the other topics - no idea...)

A demo:

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

Output:

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;

Live Demo on Compiler Explorer

Notes:

  1. The copy constructor and assignment operator of Object are deleted intenionally. Derived classes may provide a copy constructor but it has to call Object::Object().

  2. The sample doesn't consider other flavors of new (e.g. operator new[] or the align variants since C++17). That should be done in productive code.

No, but you can prevent creation of objects on heap/stack.

  1. To prevent creation on stack make the destructor private/protected:

    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. To prevent creation on heap make the new operators private/protected:

    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;
    

Regarding your edits, and just for fun:

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 );
  //...
}
Related