1

It sounds weird, I guess, but I'm creating some low-level code for a hardware device. Dependend on specific conditions I need to allocate more space than the actual struct needs, store informations there and pass the address of the object itself to the caller.

When the user is deallocating such an object, I need to read these informations before I actually deallocate the object.

At the moment, I'm using simple pointer operations to get the addresses (either of the class or the extra space). However, I tought it would be more understandable if I do the pointer arithmetics in member functions of an internal (!) type. The allocator, which is dealing with the addresses, is the only one who know's about this internal type. In other words, the type which is returned to the user is a different one.

The following example show's what I mean:

struct foo
{
    int& get_x() { return reinterpret_cast<int*>(this)[-2]; }
    int& get_y() { return reinterpret_cast<int*>(this)[-1]; }

    // actual members of foo

    enum { size = sizeof(int) * 2 };
};


int main()
{
    char* p = new char[sizeof(foo) + foo::size];
    foo* bar = reinterpret_cast<foo*>(p + foo::size);

    bar->get_x() = 1;
    bar->get_y() = 2;

    std::cout << bar->get_x() << ", " << bar->get_y() << std::endl;

    delete p;
    return 0;
}

Is it arguable to do it in that way?

0xbadf00d
  • 17,405
  • 15
  • 67
  • 107
  • Related: [Overallocating with `new`/`delete`](http://stackoverflow.com/questions/5520591/overallocating-with-new-delete). – Xeo Jul 05 '11 at 12:54
  • As `size` is an `enum` and thus constant, why don't you just append an array to the end of your struct? Besides, `reinterpret_cast` is not the ideal way to deal with memory allocated as some other type (though it will probably work most of the time, given trivial constructors). [placement new](http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10) would be better style. One day you might change your class and find that the cast doesn't properly initialize it. Let's also hope you never add any virtual functions. – Damon Jul 05 '11 at 12:56
  • @Damon - It's allocated as a char type. Shouldn't it be okay to use reinterpret_cast with char pointers? – 0xbadf00d Jul 05 '11 at 13:13
  • @FrEEzE2046 You should take under consideration alignment issues. malloc() will always return memory that sufficiently aligned for anything you might put on it, but that is not true for any location that is not the beginning of the returned memory. – selalerer Jul 05 '11 at 13:23
  • @FrEEzE2046: It depends what you consider "ok". Placement new will tell the compiler "create a properly working object of type X, and use this address". `reinterpret_cast` literally barfs some memory address at the compiler and tells it "this is X". If you don't have a constructor or virtual functions, the result will be the same. However, placement new leaves no open questions, it is guaranteed to work. If you ever add a virtual function a year from now, placement new will make sure your object has the proper vtable set up. `reinterpret_cast` will just crash. – Damon Jul 05 '11 at 13:27

4 Answers4

2

It seems needlessly complex to do it this way. If I were to implement something like this, I would take a simpler approach:

#pragma pack(push, 1)
struct A
{
   int x, y;
};

struct B
{
   int z;
};
#pragma pack(pop)

// allocate space for A and B:
unsigned char* data = new char[sizeof(A) + sizeof(B)];

A* a = reinterpret_cast<A*>(data);
B* b = reinterpret_cast<B*>(a + 1);

a->x = 0;
a->y = 1;
b->z = 2;

// When deallocating:
unsigned char* address = reinterpret_cast<unsigned char*>(a);

delete [] address;

This implementation is subtly different, but much easier (in my opinion) to understand, and doesn't rely on intimate knowledge of what is or is not present. If all instances of the pointers are allocated as unsigned char and deleted as such, the user doesn't need to keep track of specific memory addresses aside from the first address in the block.

Chad
  • 18,706
  • 4
  • 46
  • 63
  • One of the extra members is used when an object was logicaly deallocated. You can think of something like a memory pool does. The addresses are reused. – 0xbadf00d Jul 05 '11 at 13:01
  • 1
    @Chad You need to make sure the address a+1 is properly aligned to hold struct B. – selalerer Jul 05 '11 at 13:20
  • 1
    I think you meant `delete [] address` as the last line. – Steve Fallows Jul 05 '11 at 14:04
  • 1
    @selalerer -- you are correct. Usually I only do this stuff when alignment has been forcefully managed. I've updated my answer... – Chad Jul 05 '11 at 19:44
0

The very straightforward idea: wrap your extra logic in a factory which will create objects for you and delete them smart way.

Gregory
  • 84
  • 3
  • Than I'm not fully understand the task. You do not want to do a "hack" but do not want to see and "hack" code even isolated in a factory... – Gregory Jul 05 '11 at 13:47
  • Probably you can use one of the operator new tricks described e.g. by scott meyers in his "more effective c++". But anyway it will be a hack - since you need to allocate different memory size for different instances of the same class. – Gregory Jul 05 '11 at 13:49
0

You can also create the struct as a much larger object, and use a factory function to return an instance of the struct, but cast to a much smaller object that would basically act as the object's handle. For instance:

struct foo_handle {};

struct foo
{
    int a;
    int b;
    int c;
    int d;

    int& get_a() { return a; }
    int& get_b() { return b; }
    //...more member methods

    //static factory functions to create and delete objects
    static  foo_handle* create_obj() { return new foo(); }
    static void delete_obj(foo_handle* obj) { delete reinterpret_cast<foo*>(obj); }
};

void another_function(foo_handle* masked_obj)
{
    foo* ptr = reinterpret_cast<foo*>(masked_obj);
    //... do something with ptr
}

int main()
{
    foo_handle* handle = foo::create_obj();
    another_function(handle);
    foo::delete_obj(handle);

    return 0;
}

Now you can hide any extra space you may need in your foo struct, and to the user of your factory functions, the actual value of the pointer doesn't matter since they are mainly working with an opaque handle to the object.

Jason
  • 31,834
  • 7
  • 59
  • 78
0

It seems your question is a candidate for the popular struct hack.

Is the "struct hack" technically undefined behavior?

Community
  • 1
  • 1
karlphillip
  • 92,053
  • 36
  • 243
  • 426