Domanda

Mi ricordo quando ero in qualche corso di programmazione C, un insegnante ha suggerito una volta che uso printf a guardare l'esecuzione di un programma che stavo cercando di eseguire il debug. Questo programma ha avuto un errore di segmentazione con una causa che non riesco a ricordare in questo momento. Ho seguito il suo consiglio e la segmentation fault scomparso. Fortunatamente, un TA intelligente mi ha detto di eseguire il debug invece di utilizzare printfs. In questo caso, è stata una cosa utile da fare.

Così, oggi ho voluto mostrare a qualcuno che l'uso di printf potrebbe potenzialmente nascondere un bug, ma non riesco a trovare quel vecchio codice che ha avuto questo bizzarro bug (funzione? Hmmm).

Domanda: Qualcuno di voi ha incontrato questo comportamento così? Come avrei potuto riprodurre qualcosa di simile?

Modifica

vedo che la mia parte domanda orienta il mio parere "Uso printf è sbagliato". Non sto dicendo che esattamente e non mi piace prendere opinioni estreme, così sto modificando la questione un po '. Sono d'accordo che printf è uno strumento buono, ma ho solo voluto ricreare un caso in cui printfs fanno un errore di segmentazione scomparire e, di conseguenza, dimostrare che bisogna stare attenti.

È stato utile?

Soluzione

Ci sono casi in cui l'aggiunta di printf chiama altera il comportamento del codice, ma ci sono anche casi in cui il debug fa lo stesso. L'esempio più importante è il debug di codice multithread, dove fermare l'esecuzione di un thread può alterare il comportamento del programma, in tal modo il bug che si sta cercando non può accadere.

Quindi, utilizzando le dichiarazioni printf ha validi motivi. Sia per eseguire il debug o printf dovrebbe essere deciso caso per caso. Si noti che i due non sono in ogni caso esclusivi - si possono il debug del codice, anche se contiene chiamate printf: -)

Altri suggerimenti

Si avrebbe avuto un momento molto difficile per convincermi a non utilizzare la registrazione (e printf in questa situazione è una aveva hoc sotto forma di registrazione) per eseguire il debug. Ovviamente per eseguire il debug di un incidente, le prime cose è quello di ottenere un backtrace e l'uso purificare o uno strumento simile, ma se la causa non è evidente la registrazione è di gran lunga uno dei migliori tool è possibile utilizzare. Un debugger consente di concentrarsi sui dettagli, la registrazione vi darà un quadro più ampio. Entrambi sono utili.

suona come voi che fare con un heisenbug .

Non credo ci sia nulla di intrinsecamente "sbagliato" con l'uso di printf come strumento di debug. Ma sì, come ogni altro strumento, ha i suoi difetti, e sì, c'è stato più di un'occaision in cui l'aggiunta di istruzioni printf ha creato un heisenbug. heisenbug Tuttavia, ho avuto anche presentarsi come risultato di layout di memoria cambiamenti introdotti da un debugger, nel qual caso printf si è rivelata preziosa nel tracciare i passaggi che portano al crollo.

IMHO Ogni sviluppatore si basa ancora qua e là sulle stampe. Abbiamo appena imparato a chiamarli "log dettagliati".

Più precisamente, il problema principale che ho visto è che le persone trattano printfs come se fossero invincibili. Per esempio, non è raro in Java per vedere qualcosa di simile

System.out.println("The value of z is " + z + " while " + obj.someMethod().someOtherMethod());

Questo è grande, se non che z è stato effettivamente coinvolto nel metodo, ma che altro oggetto non era, e non c'è per garantire che non sarà possibile ottenere una deroga dall'espressione del obj.

Un'altra cosa che le stampe fanno è che essi introducono ritardi. Ho visto codice con condizioni di gara a volte "get fisse" quando vengono introdotti stampe. Non sarei sorpreso se alcuni usi di codice che.

Mi ricordo una volta cercando di eseguire il debug di un programma sul Macintosh (circa 1991) in cui il codice di pulitura generato del compilatore per uno stack frame tra 32K e 64K era errata perché ha utilizzato un'aggiunta indirizzo a 16 bit invece che a 32 bit uno (quantità a 16 bit aggiunto a un registro di indirizzi sarà sign-esteso sulla 68000). La sequenza era qualcosa come:

  copy stack pointer to some register
  push some other registers on stack
  subtract about 40960 from stack pointer
  do some stuff which leaves saved stack-pointer register alone
  add -8192 (signed interpretation of 0xA000) to stack pointer
  pop registers
  reload stack pointer from that other register

L'effetto netto è che tutto andava bene tranne che i registri salvati stati danneggiati, e uno di loro ha tenuto una costante (l'indirizzo di una matrice globale). Se il compilatore ottimizza una variabile ad un registro nel corso di una sezione di codice, si segnala che nel file di debug-informazione in modo che il debugger può correttamente emetterlo. Quando una costante viene in modo ottimizzato, il compilatore a quanto pare non include tali informazioni, dal momento che non ci dovrebbe essere bisogno. Ho rintracciato le cose facendo una "printf" dell'indirizzo della matrice, e impostare punti di interruzione in modo da poter visualizzare l'indirizzo prima e dopo la printf. Il debugger correttamente riportato l'indirizzo prima e dopo la printf, ma il printf emesso il valore sbagliato, così ho smontato il codice e ho visto che stava spingendo printf registro A3 nello stack; visualizzazione registro A3 prima della printf ha dimostrato di avere un valore piuttosto diverso dall'indirizzo dell'array (printf mostrato il valore A3 effettiva tenuta).

Non so come mi sarei mai rintracciato che uno giù se non fossi stato in grado di utilizzare sia il debugger e insieme printf (o, se è per questo, se non avessi capito 68000 codice assembly).

Sono riuscito a fare questo. Stavo leggendo dati da un file flat. Il mio algoritmo difettoso è andato come segue:

  1. ottenere lunghezza del file di input in byte
  2. allocare una matrice di lunghezza variabile di caratteri per servire come un buffer
    • i file sono di piccole dimensioni, quindi non sono preoccupato per overflow dello stack, ma per quanto riguarda i file di input di lunghezza zero? oops!
  3. restituire un codice di errore se la lunghezza file di input è 0

ho scoperto che la mia funzione in modo affidabile sarebbe gettare un guasto seg - a meno che non ci fosse un qualche printf nel corpo della funzione, nel qual caso avrebbe funzionato esattamente come volevo. La correzione per il guasto seg era di destinare la lunghezza del file più uno al punto 2.

Ho appena avuto un'esperienza simile. Ecco il mio problema specifico, e la causa:

// Makes the first character of a word capital, and the rest small
// (Must be compiled with -std=c99)
void FixCap( char *word )
{
  *word = toupper( *word );
  for( int i=1 ; *(word+i) != '\n' ; ++i )
    *(word+i) = tolower( *(word+i) );
}

Il problema è con la condizione del ciclo - ho usato '\ n' al posto del carattere nullo, '\ 0'. Ora, io non so esattamente come funziona printf, ma da questa esperienza che sto indovinando che utilizza alcuni posizione di memoria dopo le mie variabili come spazio temporaneo / lavoro. Se un printf risultati dichiarazione in un 'n \' carattere scritti in qualche luogo dopo in cui è memorizzato mia parola, la funzione FixCap sarà in grado di fermare a un certo punto. Se rimuovo il printf, poi continua a loop, alla ricerca di un '\ n', ma mai trovarlo, fino a quando non segfaults.

Così, alla fine, la causa principale del mio problema è che a volte si digita '\ n' quando voglio dire '\ 0'. E 'un errore che ho fatto prima, e probabilmente uno farò di nuovo. Ma ora so a cercarlo.

Be ', forse si poteva insegnargli come utilizzare gdb o di altri programmi di debug? Digli che se un bug scomparire juste grazie ad un "printf", allora non ha davvero scompaiono e potrebbe apparire di nuovo quest'ultimo. Un bug dovrebbe essere risolto, non ignorato.

Questo vi darà una divisione per 0 quando si rimuove la linea printf:

int a=10;
int b=0;
float c = 0.0;

int CalculateB()
{
  b=2;
  return b;
}
float CalculateC()
{
  return a*1.0/b;
}
void Process()
{
  printf("%d", CalculateB()); // without this, b remains 0
  c = CalculateC();
}

Quale sarebbe il caso di debug? Stampa di una matrice char *[] prima di chiamare exec() solo per vedere come è stato formato token -. Credo che questo è un uso abbastanza valido per printf()

Tuttavia, se il formato alimentata printf() è di costo e complessità che si può effettivamente esecuzione alter programma (velocità, soprattutto), un debugger può essere il modo migliore per andare sufficiente. Poi di nuovo, debugger e profiler anche hanno un costo. O si può esporre corse potrebbero non affiorare in loro assenza.

Tutto dipende da ciò che si sta scrivendo e il bug che si sta inseguendo. Gli strumenti disponibili sono debugger, printf() (raggruppamento logger in printf pure) affermazioni e profiler.

È un cacciavite a lama meglio di altri generi? Dipende da quello che vi serve. Nota, non sto dicendo affermazioni sono buoni o cattivi. Sono solo un altro strumento.

Un modo per affrontare questo è quello di istituire un sistema di macro che lo rende facile per spegnere printfs w / o dover eliminarli nel codice. Io uso qualcosa di simile:

#define LOGMESSAGE(LEVEL, ...) logging_messagef(LEVEL, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);

/* Generally speaking, user code should only use these macros.  They
 * are pithy. You can use them like a printf:
 *
 *    DBGMESSAGE("%f%% chance of fnords for the next %d days.", fnordProb, days);
 *
 * You don't need to put newlines in them; the logging functions will
 * do that when appropriate.
 */
#define FATALMESSAGE(...) LOGMESSAGE(LOG_FATAL, __VA_ARGS__);
#define EMERGMESSAGE(...) LOGMESSAGE(LOG_EMERG, __VA_ARGS__);
#define ALERTMESSAGE(...) LOGMESSAGE(LOG_ALERT, __VA_ARGS__);
#define CRITMESSAGE(...) LOGMESSAGE(LOG_CRIT, __VA_ARGS__);
#define ERRMESSAGE(...) LOGMESSAGE(LOG_ERR, __VA_ARGS__);
#define WARNMESSAGE(...) LOGMESSAGE(LOG_WARNING, __VA_ARGS__);
#define NOTICEMESSAGE(...) LOGMESSAGE(LOG_NOTICE, __VA_ARGS__);
#define INFOMESSAGE(...) LOGMESSAGE(LOG_INFO, __VA_ARGS__);
#define DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#if defined(PAINFULLY_VERBOSE)
#   define PV_DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#else
#   define PV_DBGMESSAGE(...) ((void)0);
#endif

logging_messagef() è una funzione definita in un file .c separato. Utilizzare il xmessage (...) le macro nel codice a seconda dello scopo del messaggio. La cosa migliore di questa configurazione è che funziona per il debug e la registrazione allo stesso tempo, e la funzione logging_messagef() può essere modificata per fare diverse cose (printf per stderr, un file di log, l'uso syslog o qualche altra funzione di registrazione del sistema, ecc), e messaggi di sotto di un certo livello può essere ignorato in logging_messagef() quando non ne hanno bisogno. PV_DBGMESSAGE() è per quei messaggi di debug abbondanti che certamente desidera disattivare in produzione.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top