malloc, calloc, free et cie ne sont que des fonctions qui te permettent de communiquer avec le gestionnaire de mémoire. Ce dernier sait tout, et lorsqu'on lui demande de libérer la mémoire d'un certain pointeur il sait combien de mémoire il lui a alloué.
Il y a plusieurs stratégies d'allocation de mémoire. La gnu libc utilise un dérivé de dlmalloc, le kernel en utilise une autre version, tu peux toi même te créer tes versions de gestion de mémoire ...
http://en.wikipedia.org/wiki/Malloc#Implementations
EDIT: Pour info, valgrind remplace les implémentations de malloc et cie pour détecter les fuites de mémoire ... encore un autre gestionnaire
En entrée de fonction, on alloue sur la pile tout ce qui faut comme place pour y stocker les variables locale. Tu peux encore l'étendre avec un alloca (=un malloc sur le stack).
En sortie de fonction, on remet la pile comme on l'a trouvée (ou presque) et toutes les variables locales (y compris celle que tu as allouées manuellement) seront automatiquement libérées.
À moins de tuner ton système, la place sur la pile est chère car relativement rare.
Si tu alloues un espace avec 1 malloc ou 1 calloc, alors il te suffira de 1 free pour libérer tout l'espace.
Si tu as un tableau multidimensionnel, tu feras plusieurs malloc et par conséquent plusieurs free (généralement dans une boucle).
Partager