Devi liberare la memoria solo se fai una malloc.

Parlando di teoria molto spicciola, in un linguaggio di programmazione (C nel tuo caso) esistono due "tipologie" di memoria:

1. Lo stack
2. Lo heap

Lo stack è la memoria che viene allocata ogni volta che richiami una funzione. Per esempio se ho un programma del genere:

Codice PHP:
int main(){
     
x(3);
     
     return 
0;
}

int x(int i){
    
j(++i);
}

int j(int i){
    return;

lo stack al momento della chiamata a j() nella funzione x() sarà:

j() --> int i = 4;
x() --> int i = 3;
main()

Ora ogni frame dello stack ha una zona di memoria che ti permette di memorizzare variabili locali e valori locali nel tuo caso

char *str = "ciao";

avresti avuto il frame main:

main() --> char *str --> 'ciao'

dove il valore del puntatore str sarebbe l'indirizzo di memoria interno al frame main dello stack.

Ora ogni volta che una funziona ritorna, il frame viene distrutto e quindi la memoria viene rilasciata. Infatti nel tuo caso proprio per questo motivo non serve liberare la memoria occupata dalla stringa. In pratica quando il tuo programma termina o più precisamente quando il main ritorna la memorica occupata da *str viene automaticamente liberata perchè il frame main dello stack viene liberato.

Qual'è il problema di questo meccanismo?
Non puoi allocarti strutture globali, a meno che non le allochi nello stack del main con ovvi problemi di gestione per le funzioni superiori al main, e inoltre allocare grandi strutture nello stack non è un modo efficente di gestione delle risorse in quanto è meglio mantenere uno stack piccolo che permette di liberare facilmente le risorse locali e di ritornare velocemente al chiamante (la funzione che alloca un frame nello stack).
Per questo motivo esiste lo Heap, una zona di memoria libera in cui allocare quello che si vuole in modo dinamico (nel senso che la memoria viene rilocata e assegnata al processo chiamante dal sistema operativo di volta in volta che se ne ha bisogno).

In C per allocare memoria nello Heap usi la funzione malloc (o uno dei sui cugini) la quale non fa null' altro che chiedere al kernel del tuo sistema operativo di assegnare una specifica quantità di memoria per il processo chiamante: infatti ti ritorna un puntatore a void che non significa puntatore a nulla, ma puntatore a memoria generalizzata.
In Java che è un linguaggio più evoluto del C si usa l' operatore new che serve a creare un oggetto. La JVM in automatico e nascosto ai nostri occhi farà una malloc per allocare la giusta dimensione della struttura da noi creata.

Il problema che sorge nell' uso di questa memoria è la frammentazione e l' uso sconsiderato che se ne può fare, cioè il dimenticarsi di liberare la memoria. In C esiste la funzione free(void *ptr) che non fa nientaltro che dire al Sistema operativo che la memoria allocata dall' indirizzo puntato da *ptr può essere riassegnata per un altro processo o sempre per lo stesso processo in una futura chiamata malloc.
In Java o comunque in tutti i linguaggi più moderni C# per esempio, è stata tolta la possibiltà al programmatore di liberare la memoria e si è introdotto un sistema automatico di liberazione della mamoria chiamato garbage collection. La garbage collection è una funzionalità, simile ad un crontab, di un linguaggio che si occupa di liberare autonomamente la memoria non più utilizzata utilizzando particolari algoritmi il più diffuso è il "mark and sweep".
I linguaggi moderni tolgono la possibilità di liberare la memoria manualmente per svariati motivi:
1. Per rendere il linguaggio più semplice.
2. Per rendere i programmi scritti in quel linguaggio più sicuri, perchè è cattiva norma di quasi tutti i programmatorii dimenticarsi da qualche parte del codice una free, il che è sintomo di cattiva programmazione e soprattutto tende a rendere programmi complessi insicuri
3. Rendere la programmazione più snella

Il problema del garbage collection è che poi i nostri programmi saranno più lenti. Vedi programmi scritti in Java.

Spero di essere stato abbastanza esaustivo.
Ciao