printf - sumber bug? [Tutup]

9

Saya menggunakan banyak printfuntuk melacak / mencatat tujuan dalam kode saya, saya telah menemukan bahwa itu adalah sumber kesalahan pemrograman. Saya selalu menemukan operator penyisipan ( <<) agak aneh tapi saya mulai berpikir bahwa dengan menggunakannya, saya bisa menghindari beberapa bug ini.

Adakah yang pernah memiliki wahyu yang sama atau saya hanya memahami sedotan di sini?

Beberapa mengambil poin

  • Garis pemikiran saya saat ini adalah bahwa keamanan jenis lebih penting daripada manfaat menggunakan printf. Masalah sebenarnya adalah string format dan penggunaan fungsi variadic yang tidak aman.
  • Mungkin saya tidak akan menggunakan <<dan varian aliran keluaran stl tapi saya pasti akan melihat ke dalam menggunakan mekanisme tipe-aman yang sangat mirip.
  • Banyak tracing / logging bersyarat tetapi saya ingin selalu menjalankan kode untuk tidak ketinggalan bug dalam pengujian hanya karena itu cabang yang jarang diambil.
John Leidegren
sumber
4
printfdi dunia C ++? Saya melewatkan sesuatu di sini?
user827992
10
@ user827992: Apakah Anda melewatkan fakta bahwa standar C ++ menyertakan pustaka standar C dengan referensi? Sangat sah untuk digunakan printfdalam C ++. (Apakah itu ide yang bagus adalah pertanyaan lain.)
Keith Thompson
2
@ user827992: printfmemang memiliki beberapa kelebihan; lihat jawaban saya.
Keith Thompson
1
Pertanyaan ini cukup batas. Pertanyaan "Apa yang kalian pikirkan" seringkali ditutup.
dbracey
1
@vutut saya kira (terima kasih atas tipnya). Saya hanya sedikit bingung dengan moderasi agresif. Itu tidak benar-benar mendorong diskusi menarik tentang situasi pemrograman yang ingin saya sampaikan lebih banyak.
John Leidegren

Jawaban:

2

printf, terutama dalam kasus di mana Anda mungkin peduli dengan kinerja (seperti sprintf dan fprintf) adalah hack yang sangat aneh. Itu terus-menerus membuat saya kagum bahwa orang-orang yang menggebrak C ++ karena kinerja miniscule overhead yang terkait dengan fungsi virtual kemudian akan pergi untuk mempertahankan io C.

Ya, untuk mengetahui format output kami, sesuatu yang dapat kita ketahui 100% pada waktu kompilasi, mari kita parsing string format fricken saat runtime di dalam tabel lompatan besar yang aneh menggunakan kode format yang tidak dapat dipahami!

Tentu saja kode format ini tidak dapat dibuat untuk mencocokkan jenis yang diwakilinya, itu akan terlalu mudah ... dan Anda diingatkan setiap kali Anda mencari apakah itu% llg atau% lg bahwa bahasa (diketik) ini membuat Anda mencari tipe secara manual untuk mencetak / memindai sesuatu, DAN dirancang untuk prosesor pra-32bit.

Saya akui bahwa penanganan lebar dan presisi format C ++ sangat besar dan bisa menggunakan sedikit gula sintaksis, tetapi itu tidak berarti Anda harus mempertahankan peretasan aneh yang merupakan sistem io utama C. Dasar-dasar absolut cukup mudah dalam kedua bahasa (walaupun Anda mungkin harus menggunakan sesuatu seperti fungsi kesalahan / aliran kesalahan kustom untuk kode debug), kasus-kasus moderat seperti regex seperti dalam C (mudah untuk menulis, sulit untuk mengurai / debug ), dan kasus kompleks tidak mungkin dalam C.

(Jika Anda menggunakan kontainer standar sama sekali, tulis sendiri beberapa operator templated cepat << kelebihan yang memungkinkan Anda melakukan hal-hal seperti std::cout << my_list << "\n";untuk debug, di mana my_list adalah tipe list<vector<pair<int,string> > >.)

jerman
sumber
1
Masalah standar pustaka C ++ adalah, bahwa kebanyakan inkarnasi diimplementasikan operator<<(ostream&, T)dengan memanggil ... yah sprintf,! Kinerja sprintftidak optimal, tetapi karena ini, kinerja iostreams secara umum bahkan lebih buruk.
Jan Hudec
@ JanHudec: Itu tidak berlaku selama sekitar satu dekade saat ini. Pencetakan yang sebenarnya dilakukan dengan panggilan sistem dasar yang sama, dan implementasi C ++ sering memanggil ke perpustakaan C untuk itu ... tapi itu tidak sama dengan routing std :: cout melalui printf.
jkerian
16

Mencampur keluaran gaya-C printf()(atau puts()atau putchar()...) dengan std::cout << ...keluaran gaya C ++ bisa tidak aman. Jika saya ingat dengan benar, mereka dapat memiliki mekanisme buffering yang terpisah, sehingga output mungkin tidak muncul dalam urutan yang dimaksud. (Seperti yang disebutkan oleh Pemrogram AP dalam komentar, atasi sync_with_stdioini).

printf()pada dasarnya tipe-tidak aman. Jenis yang diharapkan untuk suatu argumen ditentukan oleh string format ( "%d"memerlukan intatau sesuatu yang dipromosikan int, "%s"membutuhkan char*yang harus menunjuk ke string gaya-C yang dihentikan dengan benar, dll.), Tetapi meneruskan jenis argumen yang salah menghasilkan perilaku yang tidak terdefinisi , bukan kesalahan yang dapat didiagnosis. Beberapa kompiler, seperti gcc, melakukan pekerjaan peringatan yang cukup baik tentang ketidakcocokan jenis, tetapi mereka dapat melakukannya hanya jika format string adalah literal atau dikenal pada waktu kompilasi (yang merupakan kasus paling umum) - dan semacamnya peringatan tidak dibutuhkan oleh bahasa. Jika Anda melewati jenis argumen yang salah, hal-hal buruk yang sewenang-wenang dapat terjadi.

Aliran C ++ I / O, di sisi lain, jauh lebih aman untuk jenis, karena <<operator kelebihan beban untuk berbagai jenis. std::cout << xtidak harus menentukan jenis x; kompiler akan menghasilkan kode yang tepat untuk tipe apa pun x.

Di sisi lain, printfopsi pemformatan adalah IMHO jauh lebih nyaman. Jika saya ingin mencetak nilai titik-mengambang dengan 3 digit setelah titik desimal, saya dapat menggunakan "%.3f"- dan itu tidak berpengaruh pada argumen lain, bahkan dalam printfpanggilan yang sama . setprecisionSebaliknya, C ++ mempengaruhi kondisi aliran, dan dapat mengacaukan keluaran nanti jika Anda tidak terlalu berhati-hati untuk mengembalikan aliran ke keadaan sebelumnya. (Ini adalah kencing hewan peliharaan pribadi saya; jika saya kehilangan beberapa cara bersih untuk menghindarinya, silakan berkomentar.)

Keduanya memiliki kelebihan dan kekurangan. Ketersediaan printfsangat berguna jika Anda memiliki latar belakang C dan Anda lebih mengenalnya, atau jika Anda mengimpor kode sumber C ke dalam program C ++. std::cout << ...lebih idiomatis untuk C ++, dan tidak memerlukan banyak perawatan untuk menghindari ketidakcocokan tipe. Keduanya valid C ++ (standar C ++ mencakup sebagian besar pustaka standar C dengan referensi).

Ini mungkin terbaik untuk menggunakan std::cout << ...untuk kepentingan C ++ programmer lain yang dapat bekerja pada kode Anda, tetapi Anda dapat menggunakan salah satu - terutama dalam kode jejak bahwa Anda akan membuang.

Dan tentu saja ada baiknya menghabiskan waktu belajar bagaimana menggunakan debugger (tapi itu mungkin tidak layak di beberapa lingkungan).

Keith Thompson
sumber
Tidak disebutkan pencampuran dalam pertanyaan awal.
dbracey
1
@ Dbracey: Tidak, tapi saya pikir itu layak disebut sebagai kemungkinan kelemahan printf.
Keith Thompson
6
Untuk masalah sinkronisasi, lihat std::ios_base::sync_with_stdio.
Pemrogram
1
+1 Menggunakan std :: cout untuk mencetak info debug di aplikasi multithread tidak 100% berguna. Setidaknya dengan printf hal-hal yang tidak mungkin disisipkan dan tidak dapat dipecahkan oleh manusia atau mesin.
James
@ James: Apakah itu karena std::coutmenggunakan panggilan terpisah untuk setiap item yang dicetak? Anda dapat mengatasinya dengan mengumpulkan garis keluaran ke dalam string sebelum mencetaknya. Dan tentu saja Anda juga dapat mencetak satu item sekaligus dengan printf; lebih mudah mencetak satu baris (atau lebih) dalam satu panggilan.
Keith Thompson
2

Masalah Anda kemungkinan besar berasal dari pencampuran dua manajer keluaran standar yang sangat berbeda, yang masing-masing memiliki agenda sendiri untuk STDOUT kecil yang malang itu. Anda tidak mendapatkan jaminan tentang bagaimana mereka diimplementasikan, dan sangat mungkin bahwa mereka mengatur opsi deskriptor file yang saling bertentangan, keduanya mencoba melakukan hal-hal yang berbeda untuk itu, dll. Juga, operator penyisipan memiliki satu lebih besar printf: printfakan membiarkan Anda melakukan ini:

printf("%d", SomeObject);

Padahal <<tidak akan.

Catatan: Untuk debugging, Anda tidak menggunakan printfatau cout. Anda menggunakan fprintf(stderr, ...)dan cerr.

Linuxios
sumber
Tidak disebutkan pencampuran dalam pertanyaan awal.
dbracey
Tentu saja Anda dapat mencetak alamat suatu objek tetapi perbedaan besar adalah bahwa printftidak aman untuk tipe dan saya pikir saat ini adalah keselamatan jenis melebihi manfaat apa pun dari penggunaan printf. Masalahnya adalah format string dan fungsi variadic tidak aman.
John Leidegren
@ JohnLeidegren: Tapi bagaimana kalau SomeObjectbukan penunjuk? Anda akan mendapatkan data biner acak yang diputuskan oleh kompilator SomeObject.
Linuxios
Saya pikir saya membaca jawaban Anda mundur ... nvm.
John Leidegren
1

Ada banyak grup - misalnya google - yang tidak suka stream.

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

(Pop membuka segitiga thingy sehingga Anda dapat melihat diskusi.) Saya pikir panduan gaya google C ++ memiliki BANYAK saran yang sangat masuk akal.

Saya pikir tradeoffnya adalah stream lebih aman tetapi printf lebih jelas untuk dibaca (dan lebih mudah untuk mendapatkan format yang Anda inginkan).

dbracey
sumber
2
Panduan gaya Google itu bagus, TAPI itu berisi beberapa item yang tidak cocok untuk panduan tujuan umum . (yang ok, karena bagaimanapun ini adalah panduan Google untuk kode yang berjalan di / untuk Google.)
Martin Ba
1

printfdapat menyebabkan bug karena kurangnya keamanan jenis. Ada beberapa cara untuk mengatasi itu tanpa beralih ke iostream's <<operator dan lebih rumit format:

  • Beberapa kompiler (seperti GCC dan Dentang) secara opsional dapat memeriksa printfstring format Anda terhadap printfargumen dan dapat menampilkan peringatan seperti berikut jika tidak cocok.
    peringatan: konversi menentukan tipe 'int' tetapi argumennya memiliki tipe 'char *'
  • The typesafeprintf naskah dapat preprocess Anda printfpanggilan -gaya untuk membuat mereka mengetik-aman.
  • Perpustakaan seperti Boost.Format dan FastFormat memungkinkan Anda menggunakan printfstring format-like (Boost.Format's hampir identik dengan printf) sambil menjaga iostreamskeamanan jenis dan jenis ekstensibilitas.
Josh Kelley
sumber
1

Sintaks printf pada dasarnya baik-baik saja, dikurangi beberapa pengetikan yang tidak jelas. Jika Anda pikir itu salah mengapa C #, Python dan bahasa lain menggunakan konstruksi yang sangat mirip? Masalah dalam C atau C ++: itu bukan bagian dari bahasa dan karenanya tidak diperiksa oleh kompiler untuk sintaks yang benar (*) dan tidak terurai menjadi serangkaian panggilan asli jika mengoptimalkan kecepatan. Perhatikan bahwa jika mengoptimalkan ukuran, panggilan printf mungkin menjadi lebih efisien! Sintaks streaming C ++ juga bagus. Berhasil, jenis-keamanan ada, tetapi sintaks verbose ... bleh. Maksudku, aku menggunakannya, tapi tanpa sukacita.

(*) beberapa kompiler MELAKUKAN pemeriksaan ini dan hampir semua alat analisis statis (saya menggunakan Lint dan tidak pernah memiliki masalah dengan printf sejak itu).

Merusak
sumber
1
Ada Boost.Format yang menggabungkan sintaks yang nyaman ( format("fmt") % arg1 % arg2 ...;) dengan tipe-safety. Dengan biaya lebih banyak kinerja, karena menghasilkan panggilan stringstream yang secara internal menghasilkan panggilan sprintf di banyak implementasi.
Jan Hudec
0

printfmenurut pendapat saya sendiri, alat output yang jauh lebih fleksibel untuk menangani variabel daripada output stream CPP. Sebagai contoh:

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. */

Namun, di mana Anda mungkin ingin menggunakan <<operator CPP adalah ketika Anda membebani itu untuk metode tertentu ... misalnya untuk mendapatkan dump objek yang menyimpan data orang tertentu, PersonData....

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

Untuk itu, akan jauh lebih efektif untuk mengatakan (dengan asumsi aobjek objek PersonData)

std::cout << a;

dari:

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

Yang pertama jauh lebih sejalan dengan prinsip enkapsulasi (Tidak perlu mengetahui secara spesifik, variabel anggota pribadi), dan juga lebih mudah dibaca.

Aviator45003
sumber
0

Anda tidak seharusnya menggunakan printfC ++. Pernah. Alasannya adalah, seperti yang Anda catat dengan benar, bahwa itu adalah sumber bug dan fakta bahwa mencetak jenis khusus, dan di C ++ hampir semuanya harus jenis khusus, adalah rasa sakit. Solusi C ++ adalah stream.

Namun ada masalah kritis yang membuat stream tidak cocok untuk output apa pun dan yang terlihat pengguna! Masalahnya adalah terjemahan. Contoh meminjam dari manual gettext mengatakan Anda ingin menulis:

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

Sekarang penerjemah Jerman datang dan berkata: Oke, dalam bahasa Jerman, pesannya seharusnya

n Zeichen lang ist die Zeichenkette ' s '

Dan sekarang Anda berada dalam masalah, karena dia membutuhkan potongan-potongan di sekitar. Harus dikatakan, bahwa bahkan banyak implementasi printfmemiliki masalah dengan ini. Kecuali jika mereka mendukung ekstensi sehingga Anda dapat menggunakannya

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

The Boost.Format mendukung format printf-gaya dan memiliki fitur ini. Jadi, Anda menulis:

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

Sayangnya itu membawa sedikit penalti kinerja, karena secara internal ia menciptakan stringstream dan menggunakan <<operator untuk memformat setiap bit dan dalam banyak implementasi <<operator panggilan internal sprintf. Saya menduga implementasi yang lebih efisien akan mungkin jika benar-benar diinginkan.

Jan Hudec
sumber
-1

Anda melakukan banyak pekerjaan yang tidak berguna, selain fakta bahwa stlitu jahat atau tidak, debug kode Anda dengan serangkaian printfhanya menambah 1 tingkat kegagalan yang mungkin.

Cukup gunakan debugger dan baca sesuatu tentang Pengecualian dan cara menangkap dan melemparnya; cobalah untuk tidak menjadi lebih bertele-tele daripada yang sebenarnya Anda butuhkan.

PS

printf digunakan dalam C, untuk C ++ yang Anda miliki std::cout

pengguna827992
sumber
Anda tidak menggunakan tracing / logging bukan debugger.
John Leidegren