I have seen a codebase recently that I fear is violating alignment constraints. I’ve scrubbed it to produce a minimal example, given below. Briefly, the players are:
-
Pool. This is a class which allocates memory efficiently, for some definition of ‘efficient’. Pool is guaranteed to return a chunk of memory that is aligned for the requested size.
-
Obj_list. This class stores homogeneous collections of objects. Once the number of objects exceeds a certain threshold, it changes its internal representation from a list to a tree. The size of Obj_list is one pointer (8 bytes on a 64-bit platform). Its populated store will of course exceed that.
-
Aggregate. This class represents a very common object in the system. Its history goes back to the early 32-bit workstation era, and it was ‘optimized’ (in that same 32-bit era) to use as little space as possible as a result. Aggregates can be empty, or manage an arbitrary number of objects.
In this example, Aggregate items are always allocated from Pools, so they are always aligned. The only occurrences of Obj_list in this example are the ‘hidden’ members in Aggregate objects, and therefore they are always allocated using placement new. Here are the support classes:
class Pool { public: Pool(); virtual ~Pool(); void *allocate(size_t size); static Pool *default_pool(); // returns a global pool }; class Obj_list { public: inline void *operator new(size_t s, void * p) { return p; } Obj_list(const Args *args); // when constructed, Obj_list will allocate representation_p, which // can take up much more space. ~Obj_list(); private: Obj_list_store *representation_p; };
And here is Aggregate. Note that member declaration member_list_store_d:
// Aggregate is derived from Lesser, which is twelve bytes in size class Aggregate : public Lesser { public: inline void *operator new(size_t s) { return Pool::default_pool->allocate(s); } inline void *operator new(size_t s, Pool *h) { return h->allocate(s); } public: Aggregate(const Args *args = NULL); virtual ~Aggregate() {}; inline const Obj_list *member_list_store_p() const; protected: char member_list_store_d[sizeof(Obj_list)]; };
It is that data member that I’m most concerned about. Here is the pseudocode for initialization and access:
Aggregate::Aggregate(const Args *args) { if (args) { new (static_cast<void *>(member_list_store_d)) Obj_list(args); } else { zero_out(member_list_store_d); } } inline const Obj_list *Aggregate::member_list_store_p() const { return initialized(member_list_store_d) ? (Obj_list *) &member_list_store_d : 0; }
You may be tempted to suggest that we replace the char array with a pointer to the Obj_list type, initialized to NULL or an instance of the class. This gives the proper semantics, but just shifts the memory cost around. If memory were still at a premium (and it might be, this is an EDA database representation), replacing the char array with a pointer to an Obj_list would cost one more pointer in the case when Aggregate objects do have members.
Besides that, I don’t really want to get distracted from the main question here, which is alignment. I think the above construct is problematic, but can’t really find more in the standard than some vague discussion of the alignment behavior of the ‘system/library’ new.
So, does the above construct do anything more than cause an occasional pipe stall?
Edit: I realize that there are ways to replace the approach using the embedded char array. So did the original architects. They discarded them because memory was at a premium. Now, if I have a reason to touch that code, I’ll probably change it.
However, my question, about the alignment issues inherent in this approach, is what I hope people will address. Thanks!
Ok – had a chance to read it properly. You have an alignment problem, and invoke undefined behaviour when you access the char array as an Obj_list. Most likely your platform will do one of three things: let you get away with it, let you get away with it at a runtime penalty or occasionally crash with a bus error.
Your portable options to fix this are:
as Arkadiy says, make your buffer an Obj_list member:
but you now don’t want to pay the cost of construction. You could mitigate this by providing an inline do-nothing constructor to be used only to create this instance – as posted the default constructor would do. If you follow this route, strongly consider invoking the dtor
before doing a placement new into this storage.
Otherwise, I think you are left with non portable options: either rely on your platform’s tolerance of misaligned accesses, or else use any nonportable options your compiler gives you.
Disclaimer: It’s entirely possible I’m missing a trick with unions or some such. It’s an unusual problem.