int *pi;
pi=malloc(sizeof(int));
*pi=5;
printf("%d\n",*pi);
Muy bien. Un puntero en memoria dinámica para un entero.
Aquí pierdes sizeof(int) bytes de memoria dinámica para el resto del programa. Has perdido el puntero antes de liberarlo y ahora apunta a la dirección de memoria 5.
¿Por qué funciona el programa? Porqué un puntero es un caso especial de dato entero sin signo así que le puedes dar cualquier número de esta clase que quieras.
Funciona porque le pides a pi que te dé su contenido como entero y eso es 5. Si le pidieras que te diera el contenido a la que apunta su puntero con *pi el S.O. te lo prohibiría por estar fuera del marco de memoria del programa.
char *cadena;
cadena=malloc(5*sizeof(char));
Muy bien. Un puntero en memoria dinámica para un entero.
Acabas de perder 5*sizeof(char) bytes de memoria dinámica al hacer que cadena apunte a otro sitio sin haber liberado la memoria anterior. Ahora cadena apunta a la zona de sólo lectura donde el programa ha guardado la cadena 'hola'. No has copia 'hola' a cadena si eso es lo que pretendías; has modificado el puntero.