A legacy project I am maintaining in my freetime uses overrides of operator delete
/ operator new
. These overrides over-allocate by some bytes using malloc
(and free
) and treat said bytes as a prefix filled with a signature different between new
and new[]
. Checking the signature on delete allows to discover accidental cases of "new
... delete[]
" or "new[]
... delete
". Returned is the malloc'ed pointer offset by the size of the prefix.
These overrides are put behind an #ifdef that does not evaluate in release mode.
I recently rewrote this part, using guidance from the relevant cppreference page and find that I am left with three questions I have trouble to satisfyingly answer. These are my questions:
- Is this code even still necessary or could I replace it with some builtin feature or plugin? (Currently using latest non-preview MSVC++ toolchain, might switch to MinGW-w64 at some indeterminate time in the future... currently the project does not compile on GCC though.)
- Did I avoid UB at least in the non-pathological use cases? (And even if, examples of such cases are welcome.)
- The original legacy code implements both
operator delete
anddelete[]
by callingfree
, essentially meaning their implementation (prefix handling aside) is identical... if that must be so, then why bother in the first place?
Of course, considerations beyond those pertaining to this list are also welcome.
Code to be reviewed:
#include <cstring>
constexpr char PREFIX_ARRAY [] = "STR2MED";
constexpr char PREFIX_OBJECT[] = "str1med";
constexpr auto OFFSET_ARRAY = std::size(PREFIX_ARRAY);
constexpr auto OFFSET_OBJECT = std::size(PREFIX_OBJECT);
using OFFSET_TYPE = decltype(OFFSET_ARRAY + OFFSET_OBJECT);
template<OFFSET_TYPE OFFSET>
inline void * guardDelete(void * pointer, const char PREFIX[OFFSET])
{
if(pointer == nullptr)
return pointer;
else
{
char * prefix = reinterpret_cast<char*>(pointer) - OFFSET;
ASSERT(std::memcmp(prefix, PREFIX, OFFSET) == 0);
return prefix;
}
}
void __cdecl operator delete (void * pointer) { std::free(guardDelete<OFFSET_OBJECT>(pointer, PREFIX_OBJECT)); }
void __cdecl operator delete[](void * pointer) { std::free(guardDelete<OFFSET_ARRAY> (pointer, PREFIX_ARRAY)); }
template<OFFSET_TYPE OFFSET>
inline void * guardNew(void * pointer, const char PREFIX[OFFSET])
{
ASSERT(pointer != nullptr); // Out of virtual memory?
std::memcpy(pointer, PREFIX, OFFSET);
return reinterpret_cast<char*>(pointer) + OFFSET;
}
void * __cdecl operator new (std::size_t size) { return guardNew<OFFSET_OBJECT>(std::malloc(size + OFFSET_OBJECT), PREFIX_OBJECT); }
void * __cdecl operator new[](std::size_t size) { return guardNew<OFFSET_ARRAY> (std::malloc(size + OFFSET_ARRAY), PREFIX_ARRAY); }
External dependency (ASSERT implementation; still legacy code; not to be reviewed):
// This spawns a handy Windows error message allowing one to start VS // and attach the debugger while the context is still intact; // with debugger already attached, it acts as a breakpoint. void _AssertDebug(char * strAssertion, char * strFile, unsigned int uLine) { static char str[1024]; sprintf(str, "\n--- Assertion failed: %s file:%s line:%u ---\nDo you still want to continue?", strAssertion, strFile, uLine); switch(MessageBox(GetActiveWindow(), str, "Fatal error!", MB_ABORTRETRYIGNORE|MB_DEFBUTTON3|MB_TOPMOST)) { case IDABORT: std::abort(); break; case IDRETRY: DebugBreak(); break; } } #define ASSERT(f) \ if (f) \ {} \ else \ _AssertDebug((#f), __FILE__, __LINE__)
Returned is the malloc'ed pointer offset by 1
Offset by 1 what? One Byte is probably wrong. One unit the size of the object is probably OK (but probably excessive). By one maximum aligned unit (std::max_align_t
) is probably the minimum without doing some fancy maths. \$\endgroup\$delete
operator (not sure if that's true with an allocation size of 1 object). But it can be very time-consuming to run a full program, so your code still has value against it. \$\endgroup\$6.11 Alignment [basic.align] Object types have alignment requirements (6.9.1, 6.9.2) which place restrictions on the addresses at which an object of that type may be allocated.
\$\endgroup\$