printf - fonte di bug? [chiuso]


9

Sto usando molto printfper scopi di traccia / registrazione nel mio codice, ho scoperto che è una fonte di errore di programmazione. Ho sempre trovato l'operatore di inserimento ( <<) un po 'strano, ma sto cominciando a pensare che usandolo invece avrei potuto evitare alcuni di questi bug.

Qualcuno ha mai avuto una rivelazione simile o sto solo afferrando le cannucce qui?

Alcuni tolgono punti

  • La mia attuale linea di pensiero è che la sicurezza del tipo supera qualsiasi vantaggio derivante dall'uso di 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 stl ma cercherò sicuramente di usare un meccanismo sicuro che è molto simile.
  • Molta traccia / registrazione è condizionale ma mi piacerebbe sempre eseguire il codice per non perdere i bug nei test solo perché è un ramo raramente preso.

4
printfnel mondo C ++? Mi manca qualcosa qui?
user827992

10
@ user827992: ti manca il fatto che lo standard C ++ include la libreria standard C per riferimento? È perfettamente legale da usare printfin C ++. (Se è una buona idea è un'altra domanda.)
Keith Thompson,

2
@ user827992: printfpresenta alcuni vantaggi; vedi la mia risposta.
Keith Thompson,

1
Questa domanda è piuttosto borderline. "Cosa ne pensate ragazzi" le domande sono spesso chiuse.
dbracey,

1
@vitaut suppongo (grazie per il suggerimento). Sono solo un po 'perplesso dalla moderazione aggressiva. In realtà non favorisce discussioni interessanti sulle situazioni di programmazione, che è ciò di cui vorrei avere di più.
John Leidegren,

Risposte:


2

printf, in particolare nei casi in cui potresti preoccuparti delle prestazioni (come sprintf e fprintf) è un trucco davvero strano. Mi sorprende costantemente il fatto che le persone che martellano il C ++ a causa delle minime spese generali relative alle funzioni virtuali continueranno a difendere lo io di C.

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

Ovviamente questi codici di formato non potrebbero essere fatti per abbinare i tipi che rappresentano, sarebbe troppo facile ... e ti viene ricordato ogni volta che cerchi se è% llg o% lg che questo linguaggio (fortemente tipizzato) ti rende capire manualmente i tipi per stampare / scansionare qualcosa, E è stato progettato per processori pre-32 bit.

Devo ammettere che la gestione del C ++ della larghezza e della precisione del formato è voluminosa e potrebbe usare un po 'di zucchero sintattico, ma ciò non significa che devi difendere il bizzarro hack che è il principale sistema io di C. Le basi assolute sono piuttosto facili in entrambe le lingue (anche se probabilmente dovresti usare qualcosa come una funzione di errore personalizzata / flusso di errori per il codice di debug comunque), i casi moderati sono simili a regex in C (facile da scrivere, difficile da analizzare / debug ) e i casi complessi impossibili in C.

(Se si utilizzano affatto i contenitori standard, scrivere alcuni sovraccarichi << operatori con templated rapidi che consentono di eseguire operazioni come il std::cout << my_list << "\n";debug, dove my_list è di tipo list<vector<pair<int,string> > >.)


1
Il problema della libreria C ++ standard è che la maggior parte delle incarnazioni implementano operator<<(ostream&, T)chiamando ... beh sprintf,! Le prestazioni di sprintfnon sono ottimali, ma a causa di ciò, le prestazioni degli iostreams sono generalmente peggiori.
Jan Hudec,

@JanHudec: Questo non è stato vero per circa un decennio a questo punto. La stampa effettiva viene eseguita con le stesse chiamate di sistema sottostanti e le implementazioni C ++ spesso chiamano le librerie C per questo ... ma non è la stessa cosa del routing std :: cout through printf.
jkerian,

16

La miscelazione di output in stile C printf()(o puts()o putchar()o ...) con std::cout << ...output in stile C ++ può non essere sicura. Se ricordo bene, possono avere meccanismi di buffering separati, quindi l'output potrebbe non apparire nell'ordine previsto. (Come menziona AProgrammer in un commento, si sync_with_stdiorivolge a questo).

printf()è fondamentalmente non sicuro. Il tipo previsto per un argomento è determinato dalla stringa di formato ( "%d"richiede uno into qualcosa che promuove int, "%s"richiede uno char*che deve puntare a una stringa di tipo C terminata correttamente, ecc.), Ma il passaggio di un tipo di argomento errato provoca un comportamento indefinito , non un errore diagnosticabile. Alcuni compilatori, come gcc, fanno un buon lavoro di avvertimento sui disallineamenti dei tipi, ma possono farlo solo se la stringa di formato è letterale o è altrimenti nota al momento della compilazione (che è il caso più comune) - e simili gli avvisi non sono richiesti dalla lingua. Se passi il tipo di argomento sbagliato, possono accadere cose arbitrariamente cattive.

L'I / O stream di C ++, d'altra parte, è molto più sicuro, poiché l' <<operatore è sovraccarico per molti tipi diversi. std::cout << xnon è necessario specificare il tipo di x; il compilatore genererà il codice giusto per qualunque tipo xabbia.

D'altra parte, printfle opzioni di formattazione sono IMHO molto più convenienti. Se voglio stampare un valore in 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 printfchiamata. Il C ++ setprecision, d'altra parte, influenza lo stato del flusso e può rovinare l'output successivo se non stai molto attento a ripristinare il flusso al suo stato precedente. (Questa è la mia pipì personale per animali domestici; se mi manca un modo pulito per evitarlo, si prega di commentare.)

Entrambi hanno vantaggi e svantaggi. La disponibilità di printfè particolarmente utile se ti capita di avere uno sfondo C e ne hai più familiarità o se stai importando il codice sorgente C in un programma C ++. std::cout << ...è più idiomatico per C ++ e non richiede troppe cure per evitare discrepanze tra i tipi. Entrambi sono C ++ validi (lo standard C ++ include la maggior parte della libreria standard C per riferimento).

E ' probabilmente meglio usare std::cout << ...per il bene di altri programmatori C ++ che possono lavorare sul vostro codice, ma è possibile utilizzare uno dei due - in particolare nel codice di traccia che si sta andando a buttare via.

E ovviamente vale la pena dedicare un po 'di tempo ad imparare come usare i debugger (ma ciò potrebbe non essere fattibile in alcuni ambienti).


Nessuna menzione di mescolare nella domanda originale.
dbracey,

1
@dbracey: No, ma ho pensato che valesse la pena menzionarlo come un possibile inconveniente di printf.
Keith Thompson,

6
Per il problema di sincronizzazione, vedere std::ios_base::sync_with_stdio.
Approgrammatore

1
+1 L'uso di std :: cout per stampare informazioni di debug in un'app multithread è inutile al 100%. Almeno con printf le cose non hanno la stessa probabilità di essere intercalate e non analizzabili dall'uomo o dalla macchina.
James,

@James: è perché std::coututilizza una chiamata separata per ogni elemento stampato? È possibile aggirare il problema raccogliendo una riga di output in una stringa prima di stamparla. E ovviamente puoi anche stampare un articolo alla volta con printf; è solo più conveniente stampare una linea (o più) in una chiamata.
Keith Thompson,

2

È molto probabile che il tuo problema derivi dalla combinazione di due gestori di output standard molto diversi, ognuno dei quali ha il suo programma per quel povero STDOUT. Non si ottengono garanzie sul modo in cui sono implementate ed è perfettamente possibile che impostino opzioni descrittive di file in conflitto, entrambi provano a fare cose diverse su di esso, ecc. Inoltre, gli operatori di inserimento hanno un aspetto importante printf: printfti permetteranno di farlo:

printf("%d", SomeObject);

Invece <<no.

Nota: per il debug, non si utilizza printfo cout. Tu usi fprintf(stderr, ...)e cerr.


Nessuna menzione di mescolare nella domanda originale.
dbracey,

Ovviamente puoi stampare l'indirizzo di un oggetto ma la grande differenza è che printfnon è sicuro per il tipo e io sono attualmente convinto che la sicurezza del tipo superi tutti i vantaggi dell'uso printf. Il problema è proprio la stringa di formato e la funzione variadic non sicura.
John Leidegren,

@JohnLeidegren: Ma cosa succede se SomeObjectnon è un puntatore? Otterrai dati binari arbitrari che il compilatore decide rappresenta SomeObject.
Linuxios,

Penso di aver letto la tua risposta al contrario ... nvm.
John Leidegren,

1

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

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Apri il triangolo in modo da poter vedere la discussione.) Penso che la guida di stile di Google C ++ abbia MOLTE consigli molto sensati.

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


2
La guida di stile di Google è carina, MA contiene alcuni elementi che non sono adatti per una guida di uso generale . (il che è ok, perché dopo tutto è la guida di Google per il codice in esecuzione su / per Google.)
Martin Ba

1

printfpuò causare bug a causa della mancanza di sicurezza del tipo. Ci sono alcuni modi di affrontare che, senza il passaggio a iostream's <<operatore e più complicata la formattazione:

  • Alcuni compilatori (come GCC e Clang) possono facoltativamente controllare le printfstringhe di formato rispetto agli printfargomenti e possono visualizzare avvisi come i seguenti se non corrispondono.
    attenzione: la conversione specifica il tipo 'int' ma l'argomento ha il tipo 'char *'
  • Lo script typesafeprintf può preelaborare le printfchiamate in stile per renderle sicure.
  • Librerie come Boost.Format e FastFormat ti permettono di usare printfstringhe di formato simili (in particolare quelle di Boost.Format sono quasi identiche a printf) mantenendo la iostreams'sicurezza del tipo e l'estensibilità del tipo.

1

La sintassi di Printf è fondamentalmente corretta, meno alcune delle digitazioni oscure. Se pensi che sia sbagliato perché C #, Python e altre lingue usano un costrutto molto simile? Il problema in C o C ++: non fa parte di un linguaggio e quindi non viene verificato dal compilatore per la sintassi corretta (*) e non scomposto in serie di chiamate native se ottimizza per la velocità. Si noti che se si ottimizzano le dimensioni, le chiamate a printf potrebbero risultare più efficienti! La sintassi dello streaming C ++ è tutt'altro che buona. Funziona, la sicurezza dei tipi è lì, ma la sintassi dettagliata ... bleh. Voglio dire, lo uso, ma senza gioia.

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


1
C'è Boost.Format che combina la comoda sintassi ( format("fmt") % arg1 % arg2 ...;) con la sicurezza del tipo. A scapito di alcune prestazioni in più, perché genera chiamate stringstream che generano internamente chiamate sprintf in molte implementazioni.
Jan Hudec,

0

printfè, a mio avviso, uno strumento di output molto più flessibile per gestire le variabili rispetto a qualsiasi output del flusso CPP. Per 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 utilizzare l' <<operatore CPP è quando lo sovraccarichi per un metodo particolare ... ad esempio per ottenere un dump di un oggetto che contiene i dati di una determinata persona, PersonData....

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 asia un oggetto di PersonData)

std::cout << a;

di:

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 dell'incapsulamento (non è necessario conoscere i dettagli, le variabili dei membri privati) ed è anche più facile da leggere.


0

Non dovresti usare printfin 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 tutto dovrebbe essere tipi personalizzati, è una seccatura. La soluzione C ++ è i flussi.

Tuttavia, esiste un problema critico che rende i flussi non adatti a qualsiasi output visibile dall'utente! Il problema sono le traduzioni. Prendendo in prestito un esempio dal manuale di gettext, si dice che si desidera 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 di mescolare i pezzi. Va detto che anche molte implementazioni printfhanno 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'", ...);

I 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();

Sfortunatamente comporta un po 'di penalità per le prestazioni, perché internamente crea un flusso di stringhe e utilizza l' <<operatore per formattare ogni bit e in molte implementazioni l' <<operatore chiama internamente sprintf. Sospetto che un'attuazione più efficiente sarebbe possibile se lo desiderasse davvero.


-1

Stai facendo un sacco di lavoro inutile, oltre al fatto che stlè male o no, esegui il debug del tuo codice con una serie di printfsolo aggiungere 1 livello in più di possibili guasti.

Basta usare un debugger e leggere qualcosa su Eccezioni e su come catturarle e lanciarle; cerca di non essere più prolisso di quanto tu debba realmente essere.

PS

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


Non si utilizza la traccia / registrazione invece di un debugger.
John Leidegren,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.