Para lhe dar uma compreensão melhor de por que isso acontece, gostaria de expandir um pouco a resposta de @ r-samuel-klatchko.
Quando você liga malloc, o que realmente está acontecendo é um pouco mais complicado do que apenas dar a você um pedaço de memória para brincar. Sob o capô, malloctambém mantém algumas informações de manutenção sobre a memória que forneceu (mais importante, seu tamanho), para que, quando você ligar free, ele saiba coisas como quanta memória deve ser liberada. Essas informações são normalmente mantidas imediatamente antes do local da memória retornado a você por malloc. Informações mais completas podem ser encontradas na internet ™ , mas a ideia (muito) básica é mais ou menos assim:
+------+-------------------------------------------------+
+ size | malloc'd memory +
+------+-------------------------------------------------+
^-- location in pointer returned by malloc
Com base nisso (e simplificando muito as coisas), quando você chama malloc, ele precisa obter um ponteiro para a próxima parte da memória que está disponível. Uma maneira muito simples de fazer isso é examinar o bit de memória anterior que ele cedeu e mover os sizebytes para baixo (ou para cima) na memória. Com esta implementação, você acaba com sua memória parecida com isto após a alocação p1, p2e p3:
+------+----------------+------+--------------------+------+----------+
+ size | | size | | size | +
+------+----------------+------+--------------------+------+----------+
^- p1 ^- p2 ^- p3
Então, o que está causando o seu erro?
Bem, imagine que seu código grava erroneamente além da quantidade de memória que você alocou (ou porque você alocou menos do que o necessário como era o seu problema ou porque está usando as condições de limite erradas em algum lugar do seu código). Diga seu código escreve tantos dados para p2que ele começa a substituir o que está em p3's sizecampo. Na próxima chamada malloc, ele irá olhar para a última localização de memória que retornou, olhar para seu campo de tamanho, mover para p3 + sizee então começar a alocar memória de lá. Como seu código foi sobrescrito size, no entanto, esse local de memória não é mais posterior à memória alocada anteriormente.
Nem é preciso dizer que isso pode causar estragos! Os implementadores de malloc, portanto, colocaram uma série de "asserções", ou verificações, que tentam fazer uma série de verificações de sanidade para detectar este (e outros problemas) se eles estiverem prestes a acontecer. No seu caso específico, essas asserções são violadas e, portanto malloc, são canceladas, informando que seu código estava prestes a fazer algo que realmente não deveria estar fazendo.
Como afirmado anteriormente, esta é uma simplificação grosseira, mas é suficiente para ilustrar o ponto. A implementação glibc de malloctem mais de 5k linhas e tem havido uma quantidade substancial de pesquisas sobre como construir bons mecanismos de alocação de memória dinâmica, portanto, cobrir tudo em uma resposta SO não é possível. Espero que isso tenha lhe dado uma visão do que realmente está causando o problema!