printf - fonte di bug? [chiuso]

9

Sto usando un sacco di printf per scopi di traccia / logging nel mio codice, ho scoperto che è una fonte di errore di programmazione. Ho sempre trovato l'operatore di inserimento ( << ) come qualcosa di strano ma sto iniziando a pensare che usandolo invece potrei evitare alcuni di questi bug.

Qualcuno ha mai avuto una rivelazione simile o mi sto solo aggrappando alle cannucce qui?

Alcuni punti da portare via

  • La mia linea di pensiero corrente è che la sicurezza del tipo supera qualsiasi beneficio di usare printf. Il vero problema è la stringa di formato e l'uso di funzioni variadiche non sicure.
  • Forse non userò << e le varianti del flusso di output di stl, ma cercherò sicuramente di utilizzare un meccanismo di sicurezza del tipo molto simile.
  • Gran parte della traccia / registrazione è condizionata, ma mi piacerebbe eseguire sempre il codice per non perdere bug nei test solo perché si tratta di un ramo raramente preso.
posta John Leidegren 16.08.2012 - 20:56
fonte

9 risposte

1

printf, in particolare nei casi in cui potresti preoccuparti delle prestazioni (come sprintf e fprintf) è un trucco davvero strano. Mi stupisce costantemente che le persone che si battono su C ++ a causa di minuscole prestazioni generali relative a funzioni virtuali continueranno a difendere C io.

Sì, per capire il formato del nostro output, qualcosa che possiamo sapere al 100% al momento della compilazione, analizziamo una stringa di formattazione in fase di esecuzione all'interno di una tabella di salto enormemente strana usando codici di formato imperscrutabili!

Ovviamente questi codici di formato non possono essere creati per abbinare i tipi che rappresentano, sarebbe troppo facile ... e ti viene ricordato ogni volta che cerchi% lg o% lg che questo (strongmente tipizzato) il linguaggio ti fa capire i tipi manualmente per stampare / scansionare qualcosa, ed è stato progettato per processori pre-32bit.

Ammetto che la gestione di larghezza e precisione del formato da parte di C ++ è ingombrante e potrebbe usare dello zucchero sintattico, ma ciò non significa che devi difendere il bizzarro trucco che è il sistema principale di C. Le basi assolute sono piuttosto semplici in entrambe le lingue (anche se probabilmente dovresti usare qualcosa come una funzione di errore personalizzata / flusso di errori per il codice di debug), i casi moderati sono di tipo regex in C (facile da scrivere, difficile da analizzare / eseguire il debug ), e le complesse cause impossibili in C.

(Se usi i contenitori standard, scrivi te stesso un gestore di operazioni rapide < < overload che ti permettono di fare cose come std::cout << my_list << "\n"; per debug, dove my_list è di tipo list<vector<pair<int,string> > > .)

    
risposta data 16.08.2012 - 23:53
fonte
16

Miscelare output C-style printf() (o puts() o putchar() o ...) con C ++ - lo stile std::cout << ... output può essere pericoloso. Se ricordo correttamente, possono avere meccanismi di buffering separati, quindi l'output potrebbe non apparire nell'ordine previsto. (Come menzionato da AProgrammer in un commento, sync_with_stdio risolve questo problema.

printf() è fondamentalmente di tipo non sicuro. Il tipo previsto per un argomento è determinato dalla stringa di formato ( "%d" richiede int o qualcosa che promuove a int , "%s" richiede un char* che deve puntare a una stringa in stile C terminata correttamente, ecc.), ma il passaggio del tipo errato di argomento comporta un comportamento indefinito, non un errore diagnosticabile. Alcuni compilatori, come gcc, fanno un buon lavoro di avvertimento sulle mancate corrispondenze di tipo, ma possono farlo solo se la stringa di formato è letterale o è altrimenti nota al momento della compilazione (che è il caso più comune) - e tale gli avvertimenti non sono richiesti dalla lingua Se passi il tipo di argomento sbagliato, possono accadere cose arbitrarie.

L'I / O del flusso di C ++, d'altra parte, è molto più sicuro dal punto di vista dei caratteri, poiché l'operatore << è sovraccarico per molti tipi diversi. std::cout << x non deve specificare il tipo di x ; il compilatore genererà il codice giusto per qualsiasi tipo di x .

D'altra parte, le opzioni di formattazione di printf sono IMHO molto più convenienti. Se voglio stampare un valore a virgola mobile con 3 cifre dopo il punto decimale, posso usare "%.3f" - e non ha alcun effetto su altri argomenti, anche all'interno della stessa chiamata printf . Il setprecision di C ++, d'altra parte, influenza lo stato del flusso e può rovinare l'output successivo se non si presta molta attenzione a ripristinare il flusso al suo stato precedente. (Questo è il mio cruccio personale, se mi manca un modo pulito per evitarlo, per favore commenta.)

Entrambi hanno vantaggi e svantaggi. La disponibilità di printf è particolarmente utile se hai uno sfondo C e se hai più familiarità con esso, o se stai importando il codice sorgente C in un programma C ++. std::cout << ... è più idiomatico per C ++ e non richiede la stessa attenzione per evitare errori di tipo. Entrambi sono C ++ validi (lo standard C ++ include la maggior parte della libreria standard C per riferimento).

È probabilmente meglio usare std::cout << ... per il piacere di altri programmatori C ++ che potrebbero lavorare sul tuo codice, ma puoi usarne uno - specialmente nel codice di traccia che stai per buttare via.

Ovviamente vale la pena dedicare un po 'di tempo a imparare a usare i debugger (ma potrebbe non essere fattibile in alcuni ambienti).

    
risposta data 16.08.2012 - 21:24
fonte
2

Esistono molti gruppi, ad esempio Google, a cui non piacciono i flussi.

link

(Apri il triangolo cosy in modo da poter vedere la discussione.) Penso che la guida in stile C ++ di Google abbia MOLTO un consiglio molto ragionevole.

Penso che il compromesso sia che i flussi sono più sicuri, ma printf è più chiaro da leggere (e più facile da ottenere esattamente la formattazione desiderata).

    
risposta data 16.08.2012 - 21:09
fonte
2

Molto probabilmente il tuo problema deriva dalla combinazione di due gestori di output standard molto diversi, ognuno dei quali ha il proprio programma per quel povero piccolo STDOUT. Non hai garanzie su come sono implementati, ed è perfettamente possibile che impostino opzioni di descrittori di file in conflitto, entrambi provano a fare cose diverse ad esso, ecc. Inoltre, gli operatori di inserimento hanno uno più del printf : printf ti consentirò di farlo:

printf("%d", SomeObject);

Mentre << non lo farà.

Nota: per il debug, non usi printf o cout . Utilizza fprintf(stderr, ...) e cerr .

    
risposta data 16.08.2012 - 21:21
fonte
1

printf può causare errori dovuti alla mancanza di sicurezza del tipo. Ci sono alcuni modi per affrontarlo senza passare all'operatore iostream di << e alla formattazione più complicata:

  • Alcuni compilatori (come GCC e Clang) possono opzionalmente controllare le stringhe di formato printf rispetto agli argomenti printf e possono visualizzare avvertimenti come i seguenti se non corrispondono.
    warning: conversion specifies type 'int' but the argument has type 'char *'
  • Lo typesafeprintf script può eseguire il preprocesso delle tue chiamate in stile printf per renderle sicure da digitare.
  • Librerie come Boost.Format e FastFormat ti consente di utilizzare le stringhe di formato printf -like (in particolare quelle di Boost.Format sono quasi identiche a printf ) mantenendo il% di sicurezza di tipo e l'estendibilità di tipo diiostreams.
risposta data 16.08.2012 - 23:18
fonte
1

La sintassi di Printf è fondamentalmente valida, a parte alcuni caratteri oscuri. Se pensi che sia sbagliato perché C #, Python e altri linguaggi usano il costrutto molto simile? Il problema in C o C ++: non fa parte di un linguaggio e quindi non viene controllato dal compilatore per la sintassi corretta (*) e non viene scomposto in serie di chiamate native se si ottimizza la velocità. Si noti che se si ottimizzano le dimensioni, le chiamate printf potrebbero risultare più efficienti! La sintassi di streaming C ++ è imho tutt'altro che buona. Funziona, la sicurezza del tipo è lì, ma la sintassi dettagliata ... bleh. Voglio dire, lo uso, ma senza gioia.

(*) alcuni compilatori Fanno questo controllo più quasi tutti gli strumenti di analisi statica (uso Lint e non ho mai avuto problemi con printf da allora).

    
risposta data 17.08.2012 - 13:42
fonte
0

printf è, a mio parere, uno strumento di output molto più flessibile per gestire le variabili rispetto a qualsiasi output di flusso CPP. Ad esempio:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

Tuttavia, dove potresti voler usare l'operatore di% CP% di co_de è quando lo sovraccarichi per un particolare metodo ... per esempio per ottenere un dump di un oggetto che contiene i dati di una particolare persona, << ....

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Per questo, sarebbe molto più efficace dire (supponendo che PersonData sia un oggetto di PersonData)

std::cout << a;

a:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

Il primo è molto più in linea con il principio di incapsulamento (non c'è bisogno di conoscere le specifiche, le variabili dei membri privati) ed è anche più facile da leggere.

    
risposta data 17.08.2012 - 02:54
fonte
0

Non dovresti usare printf in C ++. Mai. Il motivo è, come hai giustamente notato, che è una fonte di bug e il fatto che la stampa di tipi personalizzati, e in C ++ quasi tutti i tipi dovrebbero essere i tipi personalizzati, è il dolore. La soluzione C ++ è i flussi.

Tuttavia c'è un problema critico che rende i flussi non adatti a qualsiasi output visibile dall'utente! Il problema sono le traduzioni. Esempio di prestito tratto dal manuale di gettext che dici di voler scrivere:

cout << "String '" << str << "' has " << str.size() << " characters\n";

Ora arriva il traduttore tedesco e dice: Ok, in tedesco, il messaggio dovrebbe essere

n Zeichen lang ist die Zeichenkette 's'

E ora sei nei guai, perché ha bisogno che i pezzi vengano mischiati. Va detto che anche molte implementazioni di printf hanno problemi con questo. A meno che non supportino l'estensione in modo da poter utilizzare

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Il Boost.Format supporta i formati in stile printf e ha questa caratteristica. Quindi scrivi:

cout << format("String '%1' has %2 characters\n") % str % str.size();

Purtroppo comporta un po 'di penalizzazione delle prestazioni, perché internamente crea un stringstream e utilizza l'operatore << per formattare ogni bit e in molte implementazioni l'operatore << chiama internamente sprintf . Sospetto che un'efficace implementazione sarebbe possibile se davvero desiderata.

    
risposta data 17.08.2012 - 14:05
fonte
-1

Stai facendo un sacco di lavoro inutile, oltre al fatto che stl è malvagio o meno, fai il debug il tuo codice con una serie di printf aggiunge solo un altro livello di possibili errori.

Basta usare un debugger e leggere qualcosa sulle eccezioni e su come catturarle e lanciarle; prova a non essere più prolisso di quello che in realtà devi essere.

PS

printf è usato in C, per il C ++ hai std::cout

    
risposta data 16.08.2012 - 21:14
fonte

Leggi altre domande sui tag