TObject::IsOnHeap usage

I was looking through the documentation on TObject, and I ran across the IsOnHeap() function. I was surprised, because I hadn’t thought it possible for a C++ object to know how it had been allocated. I started playing around with it, and found some rather inconsistent results, as shown below. The left is how the object was constructed, and the right is whether TObject::IsOnHeap believes it to be on the heap or on the stack.

stack: stack new: heap shared_ptr (make_shared): stack shared_ptr (new): heap unique_ptr: heap placement new onto stack: stack placement new onto heap: stack On stack, in container: stack On heap, in container: stack On stack, in containerobj: stack On heap, in containerobj: heap modified stack: heap

Given how unreliable this function is, for what purposes should it be used? Some of the test results differ depending on whether I compile with or without optimization, so it must be relying on undefined behavior.

The testing code is below, and was run on Debian 8, using g++ v4.9.2. This behavior was the same in both root 5.34/36 and 6.06/02.

[code]// Compile with
// g++ -std=c++11 Program.cc -o Program $(root-config --cflags --libs)

#include
#include

#include “TObject.h”

std::string string_result(bool is_on_heap) {
return is_on_heap ? “heap” : “stack”;
}

void funcA() {
char buf[6sizeof(TObject)];
memset(buf, 0x99, 3
sizeof(TObject));
}

void funcB() {
TObject obj[3];
std::cout << "modified stack: " << string_result(obj[0].IsOnHeap()) << std::endl;
}

struct Container {
TObject obj;
};

struct ContainerObj : TObject {
TObject obj;
};

int main(){
{
TObject obj;
std::cout << "stack: " << string_result(obj.IsOnHeap()) << std::endl;
}

{
TObject* obj = new TObject;
std::cout << "new: " << string_result(obj->IsOnHeap()) << std::endl;
delete obj;
}

{
auto obj = std::make_shared();
std::cout << "shared_ptr (make_shared): " << string_result(obj->IsOnHeap()) << std::endl;
}

{
auto obj = std::shared_ptr(new TObject);
std::cout << "shared_ptr (new): " << string_result(obj->IsOnHeap()) << std::endl;
}

{
auto obj = std::unique_ptr(new TObject);
std::cout << "unique_ptr: " << string_result(obj->IsOnHeap()) << std::endl;
}

{
char buf[sizeof(TObject)];
TObject* obj = new(buf) TObject;
std::cout << "placement new onto stack: " << string_result(obj->IsOnHeap()) << std::endl;
obj->~TObject();
}

{
void* buf = malloc(sizeof(TObject));
TObject* obj = new(buf) TObject;
std::cout << "placement new onto heap: " << string_result(obj->IsOnHeap()) << std::endl;
obj->~TObject();
free(buf);
}

{
Container c;
std::cout << "On stack, in container: " << string_result(c.obj.IsOnHeap()) << std::endl;
}

{
Container* c = new Container;
std::cout << "On heap, in container: " << string_result(c->obj.IsOnHeap()) << std::endl;
delete c;
}

{
ContainerObj c;
std::cout << "On stack, in containerobj: " << string_result(c.obj.IsOnHeap()) << std::endl;
}

{
ContainerObj* c = new ContainerObj;
std::cout << "On heap, in containerobj: " << string_result(c->obj.IsOnHeap()) << std::endl;
delete c;
}

funcA();
funcB();
}
[/code]

Hi,

Short: it’s terrible.

The root cause is that ROOT uses global lists to implement automatic (…) memory management, and to know whether it needs to delete such an automatically managed object it needs to know whether that was actually created on the heap or not.

As you found out that’s not a question that one can really answer with C++. The current implementation gets us fairly far (most people don’t have significant memory leaks). But this, and the fact that we need to register (and de-register) objects in global collections for memory management, and “uniquify” them upon tear-down and collect everything everywhere means that we spend precious cycles.

Nowadays (since five years), C++ has a better solution, and that’s why we are moving towards a completely different interface style. See root.cern.ch/root-7 - no more TObject and thus also no more kIsOnHeap.

Good question, spot on! :slight_smile:

Cheers, Axel.

Hi,

[quote]Given how unreliable this function is, for what purposes should it be used? [/quote]For all intent and purposes, the current implementation is detecting whether TObject’s ‘operator new’ was (or was not) called to allocate the memory object.

Cheers,
Philippe.