В дополнение к ответу Mehrdads, в gcc с glibc структуры данных, представляющие кучу памяти, сохраняются в возвращаемом блоке памяти, чтобы сохранить память (то есть, это навязчивый список). Таким образом, когда блок памяти освобождается, он добавляется в свободный список. Я предполагаю, что 0, записываемый после освобождения, указывает, что это последний элемент списка свободных блоков (первое слово размером с указатель освобожденной памяти будет содержать списки next
указатель).
Если бы вам пришлось выделить и освободить больше памяти, прежде чем снова разыменовывать этот блок, значение изменилось бы, когда новый элемент добавлялся в конец списка свободных. Это один из способов, которым решения по реализации библиотеки влияют на то, что происходит во время «неопределенного» поведения. В этом случае разработчики glibc воспользовались тем фактом, что это поведение не определено, чтобы сделать их распределитель памяти более эффективным.
Если вы запустите вашу программу под valgrind, она поймает эти ошибки для вас. В любом случае, всегда держитесь подальше от неопределенного поведения, поскольку весьма вероятно, что оно будет различным на разных платформах и даже на разных сборках на одной и той же платформе (например, debug vs release).