OK, algumas respostas sobre o malloc já foram publicadas.
A parte mais interessante é como o free funciona (e nessa direção, o malloc também pode ser melhor entendido).
Em muitas implementações malloc / free, free normalmente não retorna a memória para o sistema operacional (ou pelo menos apenas em casos raros). O motivo é que você terá lacunas na pilha e, assim, pode acontecer, que você acaba com 2 ou 4 GB de memória virtual com lacunas. Isso deve ser evitado, pois assim que a memória virtual terminar, você terá um grande problema. A outra razão é que o sistema operacional pode lidar apenas com pedaços de memória com tamanho e alinhamento específicos. Para ser específico: Normalmente, o sistema operacional pode lidar apenas com blocos que o gerenciador de memória virtual pode lidar (geralmente múltiplos de 512 bytes, por exemplo, 4KB).
Portanto, retornar 40 bytes ao sistema operacional simplesmente não funcionará. Então, o que o livre faz?
O Free colocará o bloco de memória em sua própria lista de bloqueios gratuitos. Normalmente, ele também tenta mesclar blocos adjacentes no espaço de endereço. A lista de bloqueios gratuitos é apenas uma lista circular de blocos de memória que possuem alguns dados administrativos no início. Esse também é o motivo pelo qual o gerenciamento de elementos de memória muito pequenos com o malloc / free padrão não é eficiente. Cada pedaço de memória precisa de dados adicionais e, com tamanhos menores, ocorre mais fragmentação.
A lista livre também é o primeiro lugar que o malloc analisa quando é necessário um novo pedaço de memória. Ele é verificado antes de solicitar nova memória do sistema operacional. Quando um pedaço é maior que a memória necessária, ele é dividido em duas partes. Um é retornado ao chamador, o outro é colocado de volta na lista gratuita.
Há muitas otimizações diferentes para esse comportamento padrão (por exemplo, pequenos pedaços de memória). Porém, como malloc e free devem ser tão universais, o comportamento padrão é sempre o substituto quando as alternativas não são utilizáveis. Também há otimizações no manuseio da lista livre - por exemplo, armazenando os chunks em listas classificadas por tamanhos. Mas todas as otimizações também têm suas próprias limitações.
Por que seu código falha:
O motivo é que, ao escrever 9 caracteres (não esqueça o byte nulo à direita) em uma área dimensionada para 4 caracteres, você provavelmente substituirá os dados administrativos armazenados em outro pedaço de memória que fica "atrás" do seu pedaço de dados ( já que esses dados costumam ser armazenados "na frente" dos blocos de memória). Quando o free tenta colocar seu pedaço na lista gratuita, ele pode tocar esses dados administrativos e, portanto, tropeçar em um ponteiro substituído. Isso irá travar o sistema.
Este é um comportamento bastante gracioso. Também vi situações em que um ponteiro fugitivo em algum lugar substituiu dados na lista sem memória e o sistema não travou imediatamente, mas algumas sub-rotinas posteriormente. Mesmo em um sistema de média complexidade, esses problemas podem ser muito, muito difíceis de depurar! No primeiro caso em que participei, levamos (um grupo maior de desenvolvedores) vários dias para descobrir o motivo do travamento - já que ele estava em um local totalmente diferente daquele indicado pelo despejo de memória. É como uma bomba-relógio. Você sabe, seu próximo "grátis" ou "malloc" falhará, mas você não sabe o porquê!
Esses são alguns dos piores problemas de C / C ++ e uma das razões pelas quais os ponteiros podem ser tão problemáticos.