Secondo me la funzione dovrebbe restituire la stringa NON come valore di ritorno, ma caricando un buffer che gli viene passato; questo sarebbe più semplice per l'utilizzatore che a questo punto non deve preoccuparsi di richiamare poi un free() sul puntatore che gli ritorni (e deciderà lui cosa passarti, se vuole o meno allocare dinamicamente).
Se vogliamo rendere ancora più semplice per l'utilizzatore, la funzione potrebbe ritornare il puntatore alla stringa che è stata passata, in modo da permettergli di inserire la chiamata all'interno di altre chiamate.
prototipo
char *intAStringa( int n, char *str, int maxstr )
es.
char locstr[80];
printf( "Il valore è %s\n", intAStringa( 10, locstr, 80 ) );
Infine, solitamente non "scomodo" mai una malloc() per piccoli buffer, preferisco definire una stringa locale (es. char tmp[80]).
Non so se questo si possa definire più "pulito" di quello che hai fatto tu, di certo semplifica di molto la gestione degli errori (la malloc() potrebbe fallire).
Se devi convertire un intero puoi conoscere a priori quale sarà la lunghezza massima della stringa che dovrai trattare e quindi potresti effettivamente dimensionare opportunamente la stringa locale. Poi la stringa locale la copi nella stringa passata (attenzione al maxstr) oppure lavori direttamente sulla stringa passata...
Spero di esserti stato d'aiuto e di averti dato qualche spunto