La questione, sui sistemi operativi attuali, è gestita a più livelli.
Livello CRT: sui sistemi operativi moderni quello che tu credi che sia l'entrypoint del programma (il main) non è davvero il punto da cui inizia l'esecuzione del tuo programma, ma in realtà la libreria di runtime del C (detta CRT sui sistemi Microsoft, libc sui sistemi Unix-like) si prende il vero entrypoint per inizializzare diverse sue strutture (ad esempio il locale corrente, stdin/stdout/stderr, eccetera). Dopo che ha fatto ciò, richiama il tuo main. Quando il main ritorna, la CRT ha ancora la possibilità di eseguire del codice di cleanup, ivi compresa la chiusura dei file lasciati aperti.

Ma non è finita qui. L'IO del C, di fatto, è costruito sulle funzioni di IO del sistema operativo, ovvero le varie CreateFile/ReadFile/WriteFile/CloseHandle su Windows, open/read/write/close sui sistemi UNIX-like. Anche queste funzioni lavorano con un handle di qualche genere, analogo al FILE * della libreria C (è un HANDLE su Windows, un semplice int su *NIX), e anche questi devono essere chiusi.
Uno stream C, di fatto, normalmente si riduce ad essere una struttura (la famosa FILE a cui le funzioni di libreria ti forniscono un puntatore opaco), che contiene la posizione corrente nello stream, la modalità con cui è stato aperto, un eventuale buffer, ... e soprattutto l'handle fornito dalle funzioni del sistema operativo. Quindi le risorse da rilasciare quando si chiude uno stream sono la memoria occupata da questa struttura e l'handle al file sottostante.
Ma sia l'una che l'altra sono, in ultima analisi, risorse gestite dal kernel del sistema operativo, e il kernel, quando termina un processo, si occupa automaticamente di rilasciare tutte le risorse associate al processo che non siano condivise con altri processi (in praticamente ogni sistema operativo ogni oggetto-kernel ha associati il relativo processo proprietario).
Pertanto, anche qualora la CRT non si preoccupasse di chiudere i file lasciati aperti dal programmatore "distratto", il sistema operativo comunque provvederebbe a rilasciare le risorse in questione.
Tuttavia, una CRT "servizievole" probabilmente non lascerà questo mestiere completamente al sistema operativo: infatti, anche se non c'è leaking di risorse, resta un problema: la CRT normalmente effettua un buffering per i fatti suoi, indipendente da quello del sistema operativo. Per cui, se lascia fare il cleanup al sistema operativo, questi si limiterà a rilasciare memoria e handle, ma non scriverà su file gli eventuali dati rimanenti nel buffer della CRT, cosa che invece una eventuale fclose richiamata automaticamente dalla CRT prima del termine del programma fa, dato che, prima di rilasciare la memoria associata al FILE * e l'handle del sistema operativo, effettua una fflush, scrivendo i dati rimanenti nel buffer sul file.