2015-01-20 22 views
10

Ho notato un comportamento molto strano di snprintf con C++ su diverse piattaforme. Si consideri il seguente codice (ad esempio minimo di lavoro che fa sì che il comportamento osservato):Comportamento snprintf imprevisto

#include <stdio.h> 

char test1[512]; 
char test2[512]; 
char test3[1024]; 
char test4[1024]; 

int main() 
{ 
    snprintf(test1, sizeof(test1), "test1"); 
    snprintf(test2, sizeof(test2), "test2"); 
    snprintf(test3, sizeof(test3), "%s %s", test1, test2); 
    return 0; 
} 

Quando in esecuzione anche se valgrind con --tool = exp-sgcheck, viene segnalato il seguente errore (per l'istruzione 3 ° snprintf):

==30302== Invalid read of size 1 
==30302== at 0x568E4EB: vfprintf (in /lib64/libc-2.19.so) 
==30302== by 0x56B7608: vsnprintf (in /lib64/libc-2.19.so) 
==30302== by 0x5695209: snprintf (in /lib64/libc-2.19.so) 
==30302== by 0x4006AD: main (1.cc:12) 
==30302== Address 0x601460 expected vs actual: 
==30302== Expected: global array "test1" of size 1,024 in object with soname "NONE" 
==30302== Actual: global array "test2" of size 512 in object with soname "NONE" 
==30302== Actual: is 0 after Expected 

Quindi passare test1 come argomento al primo% s porta a una lettura dopo la fine dell'array test1.

Questo comportamento ha causato diversi errori di pagina in un driver di Windows (sì, so che sono dati statici ...). Fortunatamente il codice è portatile e quando è stato portato su Linux valgrind ha segnalato questo errore.

Ma per quanto ne so, snprintf dovrebbe terminare test1 con \ 0 al sesto byte (che fa, controllato che). Quindi, perché la terza istruzione snprintf viene letta dopo la fine dell'array test1? Modifica della dichiarazione 3 ° snprintf a

snprintf(test3, sizeof(test3), "%.512s %s", test1, test2); 

risolve il problema su entrambe le piattaforme. La compilazione del codice come codice C (non C++) non genera errori.

UPDATE: Su linux (e forse anche Windows) l'errore si verifica solo se il codice è compilato con le informazioni di debug incluse e le ottimizzazioni disabilitate (-g -O0 per gcc).

+1

Non che in questo caso dovrebbe fare la differenza, ma il nome del file sorgente viene chiamato nell'output di valgrind come '1.cc'. C'è qualche ragione per scrivere C e compilarlo con un compilatore C++? – Blrfl

+1

Ho appena testato il tuo codice con 'valgrind --tool = exp-sgcheck' e non sono stati segnalati errori, quale versione di' valgrind' stai usando e quale versione di 'glibc', anche qual è il tuo Linux Distro? –

+1

Puoi dirci qualcosa di più. per esempio. la tua piattaforma, la versione del compilatore, come compilare il codice. – nos

risposta

1

Dal momento che gli oggetti globali (come gli array nel tuo esempio) sono 0-inizializzato l'ultimo snprintf dovrebbe mai letto oltre la fine della stringa, indipendentemente dal fatto che i precedenti sprintfs copiato il terminazione 0 char o meno. L'unica spiegazione è che il precedente snprintfs copiasse molto più del "test1" inviato al target test1, sovrascrivendo tutti gli 0 con non-0 (non sarebbero stati improbabili 0 con memoria casuale).

Questo è molto improbabile: un bug così ovvio sarebbe stato trovato prima. Per quanto riguarda l'errore nel driver, sospetto che la memoria venga sovrascritta da un "processo" completamente non correlato (in senso generale, forse un altro driver). Per un'applicazione desktop non ho alcuna spiegazione sul perché non riuscirebbe. Provare il tuo esempio su Codingground con gcc 4.8.3 stava andando bene e ho stampato le stringhe attese quando ho aggiunto un printf() alla fine.

Btw, non sorprende che il codice originale funzioni bene con le ottimizzazioni abilitate: poiché non c'è alcun effetto osservabile il compilatore può emettere solo un NOP.

+0

Un giorno fa avrei perfettamente accettato la tua risposta;) Ma ora ho alcuni crashdump che puntano esattamente all'istruzione snprintf con un PAGE_FAULT direttamente dopo la sezione statica dei dati del driver. E come accennato in precedenza, la modifica dello snprintf ha risolto questo problema. Anche l'incidente è successo subito dopo l'avvio del driver, quindi non c'era nulla di necessario per attivare questo bug. Ma sono d'accordo sul fatto che qui le circostanze siano molto complicate, e dopo tutto sembra essere un bug nel compilatore ms C++. Purtroppo non c'è valgrind per windows :( – Johannes

+0

gcc 4.9.x su cygwin e VS 2013 esegui l'esempio bene (ma non valgrind su Windows, quindi posso solo eseguire il prog). Puoi davvero riprodurre un errore in un normale programma per lo spazio utente? EDIT: Ho compilato come C, rileggendo il tuo post originale che non era rilevante ;-) EDIT2: VS2013 come C++ funziona egregiamente. –

+0

Non ero in grado di riprodurre questo bug nello spazio utente (cioè causare un arresto anomalo). Tuttavia, nella maggior parte dei casi, non genererebbe un arresto anomalo, poiché è molto probabile che la memoria di paging segua gli ultimi dati statici dichiarati. Per creare un caso di test adatto, sarebbe molto utile avere qualcosa come valgrind per elaborare ulteriormente in quali condizioni si verifica l'accesso non valido alla lettura e come può essere inserito in una memoria non salvata (onorando cose come l'allineamento e la dimensione della pagina). Ma non posso rivelare il codice originale qui, poiché è un prodotto commerciale. – Johannes