Critique my heap debugger
I wrote the following heap debugger in order to demonstrate memory leaks, double deletes and wrong forms of deletes (i.e. trying to delete an array with `delete p` instead of `delete[]` p) to beginning programmers.

I would love to get some feedback on that from strong C++ programmers because I have never done this before and I'm sure I've done some stupid mistakes. Thanks!

    #include <cstdlib>
    #include <iostream>
    #include <new>
    
    namespace
    {
        const int ALIGNMENT = 16;
        const char* const ERR = "*** ERROR: ";
        int counter = 0;
    
        struct heap_debugger
        {
            heap_debugger()
            {
                std::cerr << "*** heap debugger started\n";
            }
    
            ~heap_debugger()
            {
                std::cerr << "*** heap debugger shutting down\n";
                if (counter > 0)
                {
                    std::cerr << ERR << "failed to release memory " << counter << " times\n";
                }
                else if (counter < 0)
                {
                    std::cerr << ERR << (-counter) << " double deletes detected\n";
                }
            }
        } instance;
    
        void* allocate(size_t size, const char* kind_of_memory, size_t token) throw (std::bad_alloc)
        {
            void* raw = malloc(size + ALIGNMENT);
            if (raw == 0) throw std::bad_alloc();
    
            *static_cast<size_t*>(raw) = token;
            void* payload = static_cast<char*>(raw) + ALIGNMENT;
    
            ++counter;
            std::cerr << "*** allocated " << kind_of_memory << " at " << payload << " (" << size << " bytes)\n";
            return payload;
        }
    
        void release(void* payload, const char* kind_of_memory, size_t correct_token, size_t wrong_token) throw ()
        {
            if (payload == 0) return;
    
            std::cerr << "*** releasing " << kind_of_memory << " at " << payload << '\n';
            --counter;
    
            void* raw = static_cast<char*>(payload) - ALIGNMENT;
            size_t* token = static_cast<size_t*>(raw);
    
            if (*token == correct_token)
            {
                *token = 0xDEADBEEF;
                free(raw);
            }
            else if (*token == wrong_token)
            {
                *token = 0x177E6A7;
                std::cerr << ERR << "wrong form of delete\n";
            }
            else
            {
                std::cerr << ERR << "double delete\n";
            }
        }
    }
    
    void* operator new(size_t size) throw (std::bad_alloc)
    {
        return allocate(size, "non-array memory", 0x5AFE6A8D);
    }
    
    void* operator new[](size_t size) throw (std::bad_alloc)
    {
        return allocate(size, "    array memory", 0x5AFE6A8E);
    }
    
    void operator delete(void* payload) throw ()
    {
        release(payload, "non-array memory", 0x5AFE6A8D, 0x5AFE6A8E);
    }
    
    void operator delete[](void* payload) throw ()
    {
        release(payload, "    array memory", 0x5AFE6A8E, 0x5AFE6A8D);
    }

Critique my heap debugger
===

I wrote the following heap debugger in order to demonstrate memory leaks, double deletes and wrong forms of deletes (i.e. trying to delete an array with `delete p` instead of `delete[]` p) to beginning programmers.

I would love to get some feedback on that from strong C++ programmers because I have never done this before and I'm sure I've done some stupid mistakes. Thanks!

    #include <cstdlib>
    #include <iostream>
    #include <new>
    
    namespace
    {
        const int ALIGNMENT = 16;
        const char* const ERR = "*** ERROR: ";
        int counter = 0;
    
        struct heap_debugger
        {
            heap_debugger()
            {
                std::cerr << "*** heap debugger started\n";
            }
    
            ~heap_debugger()
            {
                std::cerr << "*** heap debugger shutting down\n";
                if (counter > 0)
                {
                    std::cerr << ERR << "failed to release memory " << counter << " times\n";
                }
                else if (counter < 0)
                {
                    std::cerr << ERR << (-counter) << " double deletes detected\n";
                }
            }
        } instance;
    
        void* allocate(size_t size, const char* kind_of_memory, size_t token) throw (std::bad_alloc)
        {
            void* raw = malloc(size + ALIGNMENT);
            if (raw == 0) throw std::bad_alloc();
    
            *static_cast<size_t*>(raw) = token;
            void* payload = static_cast<char*>(raw) + ALIGNMENT;
    
            ++counter;
            std::cerr << "*** allocated " << kind_of_memory << " at " << payload << " (" << size << " bytes)\n";
            return payload;
        }
    
        void release(void* payload, const char* kind_of_memory, size_t correct_token, size_t wrong_token) throw ()
        {
            if (payload == 0) return;
    
            std::cerr << "*** releasing " << kind_of_memory << " at " << payload << '\n';
            --counter;
    
            void* raw = static_cast<char*>(payload) - ALIGNMENT;
            size_t* token = static_cast<size_t*>(raw);
    
            if (*token == correct_token)
            {
                *token = 0xDEADBEEF;
                free(raw);
            }
            else if (*token == wrong_token)
            {
                *token = 0x177E6A7;
                std::cerr << ERR << "wrong form of delete\n";
            }
            else
            {
                std::cerr << ERR << "double delete\n";
            }
        }
    }
    
    void* operator new(size_t size) throw (std::bad_alloc)
    {
        return allocate(size, "non-array memory", 0x5AFE6A8D);
    }
    
    void* operator new[](size_t size) throw (std::bad_alloc)
    {
        return allocate(size, "    array memory", 0x5AFE6A8E);
    }
    
    void operator delete(void* payload) throw ()
    {
        release(payload, "non-array memory", 0x5AFE6A8D, 0x5AFE6A8E);
    }
    
    void operator delete[](void* payload) throw ()
    {
        release(payload, "    array memory", 0x5AFE6A8E, 0x5AFE6A8D);
    }
