"undefined behaviour" simply means the language spec makes no gaurantees as to what will happen. It doesn't nessacerally mean that something bad will happen.
So whose job is it to remember these things? Does the OS keep some type of record in the background? (I mean, I realise that I started this post by saying that what happens is undefined, but the fact is, the 'killing spree' scenario doesn't happen, so therefore in the practical world someone is remembering.)
There are typically two layers here. The underlying memory manager and the C++ implementation.
Most memory managers were designed to meet the needs of the C language. In C the "free" function does not require the user to specify the size of the block. Therefore the memory manager will remember (among other things) the size of the block of memory that was allocated. This may be larger than the block the C++ implementation asked for. Typically the memory manager will store it's metadata before the allocated block of memory.
C++ has a culture of "you only pay for what you use". Therefore the C++ implementation will generally only remember the size of the array if it needs to do so for it's own purposes, typically because the type has a non-trival destructor.
So for types with a trivial destructor the implementation of "delete" and "delete []" is typically the same. The C++ implementation simply passes the pointer to the underlying memory manager. Something like
free(p)
On the other hand for types with a non-trivial destructor "delete" and "delete []" are likely to be different. "delete" would be somthing like (where T is the type that the pointer points to)
p->~T();
free(p);
While "delete []" would be something like.
size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);