2010-04-19 7 views
7

Quindi ho iniziato a capire come implementare la parte stdio di libc e mi sono imbattuto in un'altra domanda. Guardando man setvbuf compaiono i seguenti:chi è il buffer setvbuf gratuito?

Quando la prima operazione di I/O si verifica su un file, malloc (3) è chiamato, e un buffer si ottiene.

Questo ha senso, il tuo programma non dovrebbe avere un malloc per l'I/O a meno che tu non lo usi effettivamente. La mia reazione istintiva a questo è che libc pulirà il suo casino qui. Che posso solo supporre lo fa perché valgrind non segnala perdite di memoria (potrebbero ovviamente fare qualcosa di sporco e non assegnarlo direttamente tramite malloc ... ma supponiamo che utilizzi letteralmente malloc per ora).

Ma, è possibile specificare il proprio buffer troppo ...

int main() { 
    char *p = malloc(100); 
    setvbuf(stdio, p, _IOFBF, 100); 
    puts("hello world"); 
} 

Oh no, perdita di memoria! valgrind lo conferma. Quindi sembra che ogni volta che stdio assegna un buffer da solo, verrà automaticamente cancellato (al più tardi all'uscita del programma, ma forse alla chiusura del flusso). Ma se si specifica esplicitamente il buffer, è necessario ripulirlo autonomamente.

C'è un problema però. La pagina man dice anche questo:

è necessario assicurarsi che lo spazio che punti BUF a esiste ancora il flusso temporale è chiuso, che avviene anche alla terminazione del programma. Ad esempio, non è valido:

Ora questo sta diventando interessante per i flussi standard. Come si può pulire correttamente un buffer allocato manualmente per loro, dal momento che sono chiusi nella terminazione del programma? Potrei immaginare un "Ripuliamolo quando chiudo flag" all'interno del file struct, ma ottenere peloso, perché se ho letto questo diritto a fare qualcosa di simile:

setvbuf(stdout, 0, _IOFBF, 0); 
printf("hello "); 
setvbuf(stdout, 0, _IOLBF, 0); 
printf("world\n"); 

causerebbe 2 assegnazioni dalla libreria standard a causa di questa frase:

Se l'argomento buf è NULL, è interessata solo la modalità ; un nuovo buffer sarà assegnato alla successiva operazione di lettura o scrittura .

EDIT: un addendum alla mia domanda. Dal momento che è chiaro che io ho bisogno di free eventuali buffer passano a setvbuf, se io in realtà lo uso su stdout c'è un modo pratico per free esso? Deve vivere fino alla fine del programma. Il meglio che posso pensare è di fclose(stdout) quindi liberarlo o utilizzare un buffer statico come alcune persone hanno menzionato. Lo chiedo perché sembra una decisione di design scomoda.

risposta

3

Anche dal man page (almeno, sul mio sistema):

Se buf non è NULL, è responsabilità del chiamante alla libera (3) questo buffer dopo la chiusura del flusso.

Cioè, lo hai mallociato, lo hai liberato.

Prima di uscire, è possibile chiudere autonomamente i flussi, consentendo così di liberare il buffer. In alternativa, è possibile scaricare gli stream e chiamare di nuovo setvbuf con un argomento del buffer NULL per tornare a un buffer gestito della libreria o all'I/O senza buffer.

+0

Risposta aggiornata per il collegamento alla pagina. È OS X, ma la pagina man proviene da FreeBSD (http://www.freebsd.org/cgi/man.cgi?query=setvbuf). – outis

+0

Difficile vedere come potrebbero essere le cose altrimenti, in quanto si potrebbe fornire un buffer statico che non deve essere liberato con free(). –

+0

@Neil: in effetti, mi chiedevo di più se lo stdlib avesse una perdita di memoria in base alla progettazione poiché non c'è modo di ottenere il buffer assegnato dallo stream. –

1

È possibile chiudere stdin, stdout e stderr esplicitamente (con fclose()).

Con la maggior parte dei sistemi operativi, la memoria heap viene rilasciata automaticamente all'uscita dal programma. Quindi non vi è alcun problema pratico con l'avere buffer non rilasciati (c'è un problema estetico, ovvero che quei buffer non rilasciati inquinano l'output di Valgrind). Il mio consiglio sarebbe quello di utilizzare i buffer statici se si sente la necessità di chiamare setvbuf() su input o output standard. I buffer statici non devono essere allocati o rilasciati e sono appropriati in quanto vi sono solo tre flussi standard e ci si preoccupa di una situazione in cui tali flussi vengono mantenuti aperti fino alla chiusura del programma.

+0

L'output di debug inquinato è un problema, perché si abitua a ignorare l'output e si può facilmente perdere un errore reale. –

+0

@Thomas: esiste una garanzia che i file vengano chiusi prima che la memoria venga liberata? – outis

+0

@outis: nei sistemi operativi in ​​cui la memoria viene rilasciata automaticamente, viene rilasciata "atomicamente", in un punto in cui il/i thread/i di esecuzione hanno cessato di esistere. Non c'è nulla che possa essere eseguito successivamente, in particolare nessun codice libc che possa (erroneamente) accedere al buffer rilasciato. In breve, "funziona". Potrebbero ancora esserci alcuni file aperti e dati bufferizzati, ma all'interno del kernel del sistema operativo, al di fuori della portata del codice applicativo. –

3

Almeno secondo lo standard C, l'ultimo scenario semplicemente non è consentito: "La funzione setvbuf può essere utilizzata solo dopo che il flusso indicato da stream è stato associato a un file aperto e prima di qualsiasi altra operazione (altro di una chiamata non riuscita a setvbuf) viene eseguita sul flusso. " (C99, §7.19.5.6/2).

Per quanto riguarda, quando per liberare la memoria nei casi più semplici, il modo è quello di chiamare atexit() registrare un callback che libererà la memoria dopo l'uscita dal main(), ma prima che il controllo viene restituito al sistema operativo.

+0

interessante. Quindi il mio ultimo esempio rientra nella categoria del comportamento non definito. –