#include 
#include 
#include 


namespace  {
    class Foo {};

    static const std::align_val_t MEMORY_ALIGNMENT_DEFAULT = std::align_val_t(alignof(std::max_align_t));
    static const size_t MEMORY_HEADER_SIZE = 8;
    
    void* alloc(size_t size, std::align_val_t align) {
        // This allocator allocates memory that has header region to deallocate only my dealloc function.
        void* memory = nullptr;
        size_t total_size = size + MEMORY_HEADER_SIZE;
        memory = malloc(total_size);
        if (!memory) {
            throw std::bad_alloc();
        }
        void* aligned_memory = static_cast(memory) + MEMORY_HEADER_SIZE;
        uintptr_t addr = reinterpret_cast(aligned_memory);
        uintptr_t aligned_addr = (addr + static_cast(align) - 1) & ~(static_cast(align) - 1);
        *reinterpret_cast(memory) = size;
        printf("Allocated memory at: %p with size: %zu\n", reinterpret_cast(aligned_addr), size);
        return reinterpret_cast(aligned_addr);
    }

    void dealloc(void* p) {
        // This dealloc function assumes that the pointer was allocated with my alloc function.
        if (p) {
            size_t* header = reinterpret_cast(static_cast(p) - MEMORY_HEADER_SIZE);
            size_t size = *header;
            printf("Deallocating memory at: %p with size: %zu\n", p, size);
            free(header);
        }
    }
}

// ============================================================================
// overriding global new/delete in C++17
void* operator new(size_t size) { return alloc(size, MEMORY_ALIGNMENT_DEFAULT); }
void* operator new(size_t size, std::align_val_t alignment) { return alloc(size, alignment); }
void* operator new(size_t size, const std::nothrow_t&) noexcept { return alloc(size, MEMORY_ALIGNMENT_DEFAULT); };
void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { return alloc(size, alignment); }

void* operator new[](std::size_t size) { return alloc(size, MEMORY_ALIGNMENT_DEFAULT); }
void* operator new[](size_t size, std::align_val_t alignment) { return alloc(size, alignment); }
void* operator new[](size_t size, const std::nothrow_t&) noexcept { return alloc(size, MEMORY_ALIGNMENT_DEFAULT); }
void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { return alloc(size, alignment); }

void operator delete(void* ptr) noexcept { dealloc(ptr); }
void operator delete(void* ptr, std::size_t size) noexcept {
    if (size == 0) {
        return;
    }
    dealloc(ptr);
}
void operator delete(void* ptr, std::align_val_t alignment) noexcept { dealloc(ptr); }
void operator delete(void* ptr, std::size_t size, std::align_val_t alignment) noexcept {
    if (size == 0) {
        return;
    }
    dealloc(ptr);
}
void operator delete(void* ptr, const std::nothrow_t&) noexcept { dealloc(ptr); }
void operator delete(void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept { dealloc(ptr); }

void operator delete[](void* ptr) noexcept { dealloc(ptr); }
void operator delete[](void* ptr, std::size_t size) noexcept {
    if (size == 0) {
        return;
    }
    dealloc(ptr);
}
void operator delete[](void* ptr, std::align_val_t alignment) noexcept { dealloc(ptr); }
void operator delete[](void* ptr, std::size_t size, std::align_val_t alignment) noexcept {
    if (size == 0) {
        return;
    }
    dealloc(ptr);
}
void operator delete[](void* ptr, const std::nothrow_t&) noexcept { dealloc(ptr); }
void operator delete[](void* ptr, std::align_val_t alignment, const std::nothrow_t&) noexcept { dealloc(ptr); }
// ============================================================================



int main(int argc, char * argv[]) {
    printf("==== Start Main Function ===\n");
    {
        printf("==== Start Memory Allocation ====\n");
        Foo* foo = new Foo();
        delete foo; // NO crash here.
        printf("==== End Memory Allocation ====\n");

        printf("==== Start String Allocation ====\n");
        ::std::string* str_ = new ::std::string("Hello, World!");
        delete str_; // No crash here.
        printf("==== End String Allocation ====\n");

        printf("==== Start Memory Stream ====\n");
        ::std::stringstream* ss_ = new ::std::stringstream();
        delete ss_; // CRASH here because default delete is called.
        printf("==== End Memory Stream ====\n");

    }
    printf("==== End Main Function ===\n");
    return 0;
}
