Can a C++ class determine whether it's on the stack or heap?
Asked Answered
M

16

47

I have

class Foo {
....
}

Is there a way for Foo to be able to separate out:

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

and

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

I want Foo to be able to do different things depending on whether it's allocated on the Stack or the Heap.

Edit:

Alof of people have asked me "why do this?"

The answer:

I'm using a ref-counted GC right now. However, I want to have ability to run mark & sweep too. For this, I need to tag a set of "root" pointers -- these are the pointers on the stack. Thus, for each class, I'd like to know whether they're in the stack or in the heap.

Mckoy answered 13/1, 2010 at 5:47 Comment(12)
Separate out, as in? Static allocations IMHO are done on the stack, and allocations like 'new' will be done on the Heap.Stench
What do you mean, "separate out"? Are you asking whether there is a way for a Foo object to determine whether it was created on the stack or the heap?Lauderdale
hmm..so you want to find out where 'foo' is allocated?Stench
Why do you need to tell those apart, whats the use-case?Dardanus
can the question just be answered? whether or not the guy knows what he is doing, this might be useful for those of us that do actually need it.Quaint
Rolls Eyes in dazed confusion :-(Holloweyed
It seems to me that anon is trying to replicate the behavior of boost::invasive_ptr, but within the class itself. Seems like a lot of trouble for no benefit, to me.Matrilineal
@Anacrolix; It can't be done portably, it won't tell you anything useful if it could be and if you think you need this you're almost certainly wrong.Ceja
The real crux there, mark and sweep is sweet but: 1. It means that everything plays nicely with it, ie all your dependencies, down to and including the STL, 2. there is this "pause the world" effects when it kicks in though I have already seen a study for Eiffel using "periods" to avoid this that claimed quite interesting results, though it necessitates a dedicated thread for the GC.Camise
This was really useful to me, because I'm doing something similar. +1Menthol
Guess based on the value of the stack pointer (obtained from non-portable inline assembly of course) and then make a reasonable guess at the stack size? And this doesn't work and helps you how?Steib
@RobertMason there is one stack per thread.Evolve
M
9

You need to actually ask us the real question(a) :-) It may be apparent to you why you think this is necessary but it almost certainly isn't. In fact, it's almost always a bad idea. In other words, why do you think you need to do this?

I usually find it's because developers want to delete or not delete the object based on where it was allocated but that's something that should usually be left to the client of your code rather than your code itself.


Update:

Now that you've clarified your reasons in the question, I apologise, you've probably found one of the few areas in which what you're asking makes sense (running your own garbage collection processes). Ideally, you'd override all the memory allocation and de-allocation operators to keep track of what is created and removed from the heap.

However, I'm not sure it's a simple matter of intercepting the new/delete for the class since there could be situations where delete is not called and, since mark/sweep relies on a reference count, you need to be able to intercept pointer assignments for it to work correctly.

Have you thought about how you're going to handle that?

The classic example:

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

will not result in a delete call.

Also, how will you detect the fact that the pointer to one of your instances is on the stack? The interception of new and delete can let you store whether the object itself is stack or heap-based but I'm at a loss as to how you tell where the pointer is going to be assigned to, especially with code like:

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

Perhaps you may want to look into C++'s smart pointers, which go a long way toward making manual memory management obsolete. Shared pointers on their own can still suffer from problems like circular dependencies but the judicious use of weak pointers can readily solve that.

It may be that manual garbage collection is no longer required in your scenario.


(a) This is known as the X/Y problem. Many times, people will ask a question that pre-supposes a class of solution whereas a better approach would be just to describe the problem with no preconceptions of what the best solution will be.

Microchemistry answered 13/1, 2010 at 5:55 Comment(6)
In a user-land mark/sweep garbage collector, I would expect that some kind of smart pointer is provided to contain pointers to collectable objects (in effect, this provides accurate marking). Your code snippets are therefore not legitimate, since they reference a gc object using only a non-gc raw pointer. A "compiler-land" implementation might use conservative marking and analyse the stack directly.Niersteiner
Overloading new is not totally reliable. You could malloc() a buffer and placement new (or just simply cast) that to a class. That would still look like a stack-based class, but it's on the heap. IMO you cannot garbage collect things created with new: you'll need your own allocation and pointer wrappers.Born
I plan to use this together with ref-counted smart pointers. That have creation, operator=, and destructor overloaded. The example above would end up being like: MyObject::Ptr x = new MyObject(); x = 0; // overloading of operator = causes x to do a ref decrement, which triggers the destructor.Mckoy
You should try boost::shared_ptr, for a more canonical and tested implementation of reference counting.Micronutrient
@Micronutrient or, in C++11, std::shared_ptr which fixed some problems with boost::shared_ptr.Joettejoey
Don't like condescending language ~ "Do really need this, why should you do this?" and xyz::shared_pointer in no way is an answer to question of memory class, but another distraction.Evolve
G
21

A hacky way to do it:

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

If the object was created on the stack it must live somewhere in the direction of the outer functions stack variables. The heap usually grows from the other side, so that stack and heap would meet somewhere in the middle.

(There are for sure systems where this wouldn't work)

Gorizia answered 13/1, 2010 at 5:58 Comment(14)
And not that I would recommend doing this for any real task, just a fun idea that came to mind.Gorizia
I didn't test it, but this might not work in a multithreaded application.Northwestward
Yeah, I know you know these things, just felt it should be said. :)Micronutrient
I am also sure he knew that you knew that he knew and was just saying.Holloweyed
They all kill themselves after 100 days: en.wikipedia.org/wiki/Common_knowledge_%28logic%29Niersteiner
I actually tried this, in about 2003. Unfortunately one of the systems it doesn't work on is pretty much any C++ compiler with optimisations switched on.Arliearliene
This will not work on any modern system, i.e. any system that supports threads.Tgroup
@NickDandoulakis why would threading break this? maybe something I am missing.Unmoved
@thang, usually the stack of each thread is also allocated somewhere in the heap, so the assumption that "heap usually grows from the other side" may not be true.Northwestward
@NickDandoulakis, that's a good point. I think we can fix this hack with another hack! If &i is near the this pointer, then we're on the stack. otherwise, heap. i think that would work better. the assumption here is that the constructor is called right after allocation of this.Unmoved
@thang, perhaps "near" can work if it's class-specific? I.e. we take into account the class definition, hidden fields like vtable, and inline the code at the top of the constructor. Can we generalize it though?Northwestward
@NickDandoulakis, I think you just have to check that it is about sizeof(*this) away give or take some bytes from stack frame set up. vtable pointer would be included in the sizeof.Unmoved
@thang, the stack frame size is tricky. It can vary a lot, depending on the implementation, the size of constructor's arguments and if the constructor is called from a subclass.Northwestward
@NickDandoulakis, all true, but it would be difficult for it to be so large to confuse the stack for the heap.Unmoved
M
9

You need to actually ask us the real question(a) :-) It may be apparent to you why you think this is necessary but it almost certainly isn't. In fact, it's almost always a bad idea. In other words, why do you think you need to do this?

I usually find it's because developers want to delete or not delete the object based on where it was allocated but that's something that should usually be left to the client of your code rather than your code itself.


Update:

Now that you've clarified your reasons in the question, I apologise, you've probably found one of the few areas in which what you're asking makes sense (running your own garbage collection processes). Ideally, you'd override all the memory allocation and de-allocation operators to keep track of what is created and removed from the heap.

However, I'm not sure it's a simple matter of intercepting the new/delete for the class since there could be situations where delete is not called and, since mark/sweep relies on a reference count, you need to be able to intercept pointer assignments for it to work correctly.

Have you thought about how you're going to handle that?

The classic example:

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

will not result in a delete call.

Also, how will you detect the fact that the pointer to one of your instances is on the stack? The interception of new and delete can let you store whether the object itself is stack or heap-based but I'm at a loss as to how you tell where the pointer is going to be assigned to, especially with code like:

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

Perhaps you may want to look into C++'s smart pointers, which go a long way toward making manual memory management obsolete. Shared pointers on their own can still suffer from problems like circular dependencies but the judicious use of weak pointers can readily solve that.

It may be that manual garbage collection is no longer required in your scenario.


(a) This is known as the X/Y problem. Many times, people will ask a question that pre-supposes a class of solution whereas a better approach would be just to describe the problem with no preconceptions of what the best solution will be.

Microchemistry answered 13/1, 2010 at 5:55 Comment(6)
In a user-land mark/sweep garbage collector, I would expect that some kind of smart pointer is provided to contain pointers to collectable objects (in effect, this provides accurate marking). Your code snippets are therefore not legitimate, since they reference a gc object using only a non-gc raw pointer. A "compiler-land" implementation might use conservative marking and analyse the stack directly.Niersteiner
Overloading new is not totally reliable. You could malloc() a buffer and placement new (or just simply cast) that to a class. That would still look like a stack-based class, but it's on the heap. IMO you cannot garbage collect things created with new: you'll need your own allocation and pointer wrappers.Born
I plan to use this together with ref-counted smart pointers. That have creation, operator=, and destructor overloaded. The example above would end up being like: MyObject::Ptr x = new MyObject(); x = 0; // overloading of operator = causes x to do a ref decrement, which triggers the destructor.Mckoy
You should try boost::shared_ptr, for a more canonical and tested implementation of reference counting.Micronutrient
@Micronutrient or, in C++11, std::shared_ptr which fixed some problems with boost::shared_ptr.Joettejoey
Don't like condescending language ~ "Do really need this, why should you do this?" and xyz::shared_pointer in no way is an answer to question of memory class, but another distraction.Evolve
B
8

The answer is no, there is no standard/portable way to do this. Hacks involving overloading the new operator tend to have holes. Hacks that depend on checking pointer addresses are OS specific and heap implementation specific, and may change with future versions of the OS. You may be comfortable with that, but I wouldn't build any sort of system around this behavior.

I would start looking at different ways to accomplish your goal - perhaps you can have a totally different type to serve as the "root" in your scheme, or require the users to (properly) annotate the stack allocated types as such with a special constructor.

Bridewell answered 13/1, 2010 at 7:33 Comment(2)
Is the new hack unreliability: how do you know if the placement new invoked will place the object on the stack or the heap ?Camise
Question is "how" to do it, not "how to do it standardly/portably".Tattan
D
8

It is possible if you compare the value of 'this' with the current value of the stack pointer. If this < sp then you have been allocated in the stack.

Try this out (using gcc in 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;
}

It should output:

Stack 
Heap 
Stack 
Stack 
Heap
Dixie answered 13/1, 2010 at 15:15 Comment(2)
Could you retype this asm() part for VC++? I'm having trouble using it under VS2008. Thanks.Depressed
Would be nice to know the preconditions, e.g. gcc runtime deploys the window's process heap or in case some OSX or linux memory management. Where would a pointer to a static variable occur in your classification? What about objects allocated in other threads / stacks?Evolve
Q
5

A more direct, and less intrusive method would be to look up the pointer in the memory region maps (such as /proc/<pid>/maps). Each thread has a region allocated to its stack. Static and global variables will live in the .bss section, constants in a rodata or const segment, and so on.

Quaint answered 13/1, 2010 at 6:48 Comment(0)
S
4

I am not positive what you are asking, but overriding the new operator may be what you are trying to do. As the only safe way to create an object on the heap in C++ is to use the new operator, you can differentiate between objects that exist on the heap versus other forms of memory. Google "overloading new in c++" for more information.

You should, however, consider if differentiating between the two types of memory is really necessary from inside the class. Having an object behave differently depending upon where it is stored sounds like a recipe for disaster if you are not careful!

Spicate answered 13/1, 2010 at 5:55 Comment(4)
Not necessarily true. Consider a vector of these objects. The data for vector may have been allocated from the heap, but the object never had new called on it.Micronutrient
Constructing objects in a vector calls placement new to construct the object. Now I'm not sure if that means you also need to provide a placement new or not... haven't had to dig that deep before.Purely
Placement-new cannot be replaced. That said, vector does not use placement-new. (Or containers, for that matter.) They call the construct method of their allocator. (Which typically calls placement-new. :P)Micronutrient
Good point about vectors, though I think you mean arrays? Allocation in an array could be prohibited by making the default constructor private, but that's ugly -- especially if the object otherwise doesn't need params in its constructor.Bass
P
4

As mentioned above, you need to control how your object is allocated through overloaded new operator. Watch out for two things however, first the 'placement new' operator that initializes your object inside the memory buffer preallocated by user; second, nothing stops the user from simply casting arbitrary memory buffer into your object type:

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

Another way is the fact that most runtimes use a bit more memory than asked when doing heap allocations. They usually place some service structure there to identify proper deallocations by pointer. You could inspect your runtime implementation for these patterns, although it will make your code really unportable, dangerous and unsupportable overkill.

Again, as mentioned above, you really are asking for solution details ("how") when you should ask about the initial problem you devised this solution for ("why").

Pinxit answered 13/1, 2010 at 6:14 Comment(0)
S
3

The meta question as asked by pax is asked "why would you want to do that" you'll likely get a more informative answer.

Now assuming you're doing this for "a good reason" (perhaps just curiousity) can get this behaviour by overriding operators new and delete, but don't forget to override all 12 variants including:

new, delete, new no throw, delete no throw, new array, delete array, new array no throw, delete array no throw, placement new, placement delete, placement new array, placement delete array.

One thing you can do is put this in a base class and derive from it.

This is kind of a pain, so what different behavior did you want?

Samsara answered 13/1, 2010 at 6:13 Comment(2)
There is one problem - placement new can be used on memory from the stack and from the heap. How to distinguish this?Colonel
Important hint, to consider all overloads. Why can't standard runtimes just publish the heap management like e.g. GMP library does?!Evolve
G
3

Nope, it can't be done reliably or sensibly.

You may be able to detect when an object is allocated with new by overloading new.

But then what if the object is constructed as a class member, and the owning class is allocated on the heap?

Here's a third code example to add to the two you've got:

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

What about static/global objects? How would you tell them apart from stack/heap ones?

You could look at the address of the object, and use that to determine if it is within the range that defines the stack. But the stack may be resized at runtime.

So really, the best answer is that "there's a reason why mark & sweep GC's aren't used with C++". If you want a proper garbage collector, use a different language, one which supports it.

On the other hand, most experienced C++ programmers find that the need for a garbage collector pretty much vanishes when you learn the necessary techniques for resource management (RAII).

Grethel answered 13/1, 2010 at 12:16 Comment(0)
G
2

A way for MFC classes:

.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;
}
Gocart answered 22/4, 2012 at 14:57 Comment(0)
T
1

Overload new() for your class. This way you'll be able to tell between heap and stack allocation, but not between stack and static/global.

Tailrace answered 13/1, 2010 at 5:50 Comment(1)
This also brings a free pain in the ass when an instance of your class is a non-static member of another class.Joettejoey
M
1

I would recommend using smart pointers instead. By design, the class should have data and information about class. Book-keeping tasks should be delegated outside the class.

overloading new and delete can lead to more holes than you can imagine.

Montagu answered 16/2, 2011 at 11:25 Comment(0)
P
1

To answer your question, a reliable way (assuming your aplication isn't using more thant one thread), assuming that everithing wich is not contained by your smart pointer isn't on the heap :

-> Overloading new, so that you ca store a list of all blocs allocated, with the size of each block. -> When the constructor of your smart pointer, search in wich block your this pointer belong. If it isn't in any block, you can say it's "on the stack" (actualy, it means it's not managed by you). Otherwise, you know where and when your pointer was allocated (if you wan't to look for orphan pointers and lasily free memory, or things like that..) It do not depend from the architechture.

Papyraceous answered 26/3, 2013 at 21:2 Comment(2)
This is the right idea, but you may also need to worry about the standard allocators as well as new. If you class contains a vector, you need to know that its storage is tracked, too. The standard allocators use ::operator new so you could just redefine that and be done.Admire
You don't overload anything for third-party componentsEvolve
R
1

There is a solution, but it forces inheritance. See Meyers, "More Effective C++", Item 27.

EDIT:
Meyers' suggestion is summarized in an article written by Ron van der Wal, which Meyers himself linked to in his blog (in this post):

Tracking heap based objects

As an alternative to the global variable approach, Meyers presents a HeapTracked class that uses a list to keep track of the addresses of class instances allocated off the heap, then uses this information to determine if a particular object resides on the heap. The implementation goes like this:

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; 

There is more to read on the original article: Ron van der Wal comments on this suggestion, and then demonstrates other alternative heap tracking methods.

Rastus answered 15/6, 2016 at 6:29 Comment(0)
A
0

Take a look at the program here: http://alumni.cs.ucr.edu/~saha/stuff/memaddr.html. With a few casts, it ouputs:

        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
Abram answered 27/3, 2013 at 6:57 Comment(0)
E
0

There are platform specific solutions:

Windows: There are system functions to query stack ranges, see question
https://mcmap.net/q/372615/-how-to-get-thread-stack-information-on-windows/9437799 . With these stack variables may be uniquely identified.

Posix: Similiarily pthread_attr_getstack() should deliver stack ranges.

From your question variables in data segments do not need to be considered, else you might iterate over the modules allocated to the process and query their memory ranges.

Evolve answered 26/6, 2024 at 16:20 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.