Mencoba membuat makro yang dapat digunakan untuk mencetak pesan debug ketika DEBUG didefinisikan, seperti kode pseudo berikut:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
Bagaimana ini bisa dilakukan dengan makro?
c
c-preprocessor
jfarrell
sumber
sumber
Jawaban:
Jika Anda menggunakan kompiler C99 atau yang lebih baru
Itu mengasumsikan Anda menggunakan C99 (notasi daftar argumen variabel tidak didukung di versi sebelumnya). The
do { ... } while (0)
idiom memastikan bahwa kode tersebut bertindak seperti sebuah pernyataan (fungsi panggilan). Penggunaan kode tanpa syarat memastikan bahwa kompilator selalu memeriksa apakah kode debug Anda valid - tetapi pengoptimal akan menghapus kode ketika DEBUG adalah 0.Jika Anda ingin bekerja dengan #ifdef DEBUG, ubah kondisi tes:
Dan kemudian gunakan DEBUG_TEST tempat saya menggunakan DEBUG.
Jika Anda bersikeras string literal untuk string format (mungkin ide yang bagus), Anda juga dapat memperkenalkan hal-hal seperti
__FILE__
,__LINE__
dan__func__
ke dalam output, yang dapat meningkatkan diagnostik:Ini bergantung pada penggabungan string untuk membuat string format yang lebih besar daripada yang ditulis programmer.
Jika Anda menggunakan kompiler C89
Jika Anda terjebak dengan C89 dan tidak ada ekstensi kompiler yang berguna, maka tidak ada cara yang sangat bersih untuk menanganinya. Teknik yang saya gunakan adalah:
Dan kemudian, dalam kode, tulis:
Tanda kurung ganda sangat penting - dan itulah sebabnya Anda memiliki notasi lucu dalam ekspansi makro. Seperti sebelumnya, kompiler selalu memeriksa kode untuk validitas sintaksis (yang baik) tetapi optimizer hanya memanggil fungsi pencetakan jika DEBUG makro mengevaluasi ke nol.
Ini memang membutuhkan fungsi dukungan - dbg_printf () dalam contoh - untuk menangani hal-hal seperti 'stderr'. Anda harus tahu cara menulis fungsi varargs, tetapi itu tidak sulit:
Anda juga dapat menggunakan teknik ini di C99, tetapi
__VA_ARGS__
teknik ini lebih rapi karena menggunakan notasi fungsi biasa, bukan hack kurung ganda.Mengapa sangat penting bahwa kompilator selalu melihat kode debug?
[ Mengomel komentar dibuat untuk jawaban lain. ]
Satu ide sentral di balik implementasi C99 dan C89 di atas adalah bahwa compiler yang tepat selalu melihat pernyataan debug seperti printf. Ini penting untuk kode jangka panjang - kode yang akan bertahan satu atau dua dekade.
Misalkan sepotong kode sebagian besar tidak aktif (stabil) selama beberapa tahun, tetapi sekarang perlu diubah. Anda mengaktifkan kembali pelacakan debugging - tetapi itu membuat frustasi harus men-debug kode debugging (pelacakan) karena mengacu pada variabel yang telah diganti nama atau mengetik ulang, selama tahun-tahun pemeliharaan stabil. Jika kompiler (pasca-prosesor) selalu melihat pernyataan cetak, itu memastikan bahwa setiap perubahan di sekitarnya tidak membatalkan diagnostik. Jika kompiler tidak melihat pernyataan cetak, ia tidak dapat melindungi Anda dari kecerobohan Anda sendiri (atau kecerobohan kolega atau kolaborator Anda). Lihat ' Praktek Pemrograman ' oleh Kernighan dan Pike, khususnya Bab 8 (lihat juga Wikipedia tentang TPOP ).
Ini pengalaman 'sudah ada, selesai' - Saya pada dasarnya menggunakan teknik yang dijelaskan dalam jawaban lain di mana non-debug build tidak melihat pernyataan seperti printf selama beberapa tahun (lebih dari satu dekade). Tapi saya menemukan saran di TPOP (lihat komentar saya sebelumnya), dan kemudian mengaktifkan beberapa kode debug setelah beberapa tahun, dan mengalami masalah dengan perubahan konteks yang melanggar debugging. Beberapa kali, setelah pencetakan selalu divalidasi telah menyelamatkan saya dari masalah di kemudian hari.
Saya menggunakan NDEBUG untuk mengontrol pernyataan saja, dan makro terpisah (biasanya DEBUG) untuk mengontrol apakah penelusuran debug dibangun ke dalam program. Bahkan ketika penelusuran debug sudah ada di dalam, saya sering tidak ingin hasil debug muncul tanpa syarat, jadi saya memiliki mekanisme untuk mengontrol apakah output muncul (level debug, dan alih-alih memanggil
fprintf()
secara langsung, saya memanggil fungsi cetak debug yang hanya mencetak dengan syarat. sehingga kode yang sama dapat dicetak atau tidak dicetak berdasarkan opsi program). Saya juga memiliki versi kode 'multi-subsistem' untuk program yang lebih besar, sehingga saya dapat memiliki bagian yang berbeda dari program yang menghasilkan jumlah jejak yang berbeda - di bawah kendali runtime.Saya menganjurkan bahwa untuk semua bangunan, kompiler harus melihat pernyataan diagnostik; Namun, kompiler tidak akan menghasilkan kode apa pun untuk pernyataan pelacakan debugging kecuali debug diaktifkan. Pada dasarnya, ini berarti bahwa semua kode Anda diperiksa oleh kompiler setiap kali Anda mengkompilasi - baik untuk rilis atau debugging. Ini hal yang bagus!
debug.h - versi 1.2 (1990-05-01)
debug.h - versi 3.6 (2008-02-11)
Varian argumen tunggal untuk C99 atau lebih baru
Kyle Brandt bertanya:
Ada satu hack sederhana dan kuno:
Solusi khusus-GCC yang ditunjukkan di bawah ini juga menyediakan dukungan untuk itu.
Namun, Anda dapat melakukannya dengan sistem C99 langsung dengan menggunakan:
Dibandingkan dengan versi pertama, Anda kehilangan pemeriksaan terbatas yang memerlukan argumen 'fmt', yang berarti bahwa seseorang dapat mencoba memanggil 'debug_print ()' tanpa argumen (tetapi tanda koma dalam daftar argumen
fprintf()
tidak akan berhasil dikompilasi) . Apakah hilangnya pemeriksaan adalah masalah sama sekali masih bisa diperdebatkan.Teknik khusus GCC untuk satu argumen
Beberapa kompiler dapat menawarkan ekstensi untuk cara lain dalam menangani daftar argumen panjang variabel dalam makro. Secara khusus, seperti yang pertama kali dicatat dalam komentar oleh Hugo Ideler , GCC memungkinkan Anda untuk menghilangkan koma yang biasanya akan muncul setelah argumen 'diperbaiki' terakhir ke makro. Ini juga memungkinkan Anda untuk menggunakan
##__VA_ARGS__
dalam teks pengganti makro, yang menghapus koma sebelum notasi jika, tetapi hanya jika, token sebelumnya adalah koma:Solusi ini mempertahankan manfaat dari memerlukan argumen format sambil menerima argumen opsional setelah format.
Teknik ini juga didukung oleh Dentang untuk kompatibilitas GCC.
Mengapa loop do-while?
Anda ingin dapat menggunakan makro sehingga terlihat seperti pemanggilan fungsi, yang berarti akan diikuti oleh titik koma. Karena itu, Anda harus mengemas tubuh makro yang sesuai. Jika Anda menggunakan
if
pernyataan tanpa di sekitarnyado { ... } while (0)
, Anda akan memiliki:Sekarang, anggaplah Anda menulis:
Sayangnya, lekukan itu tidak mencerminkan kontrol aliran yang sebenarnya, karena preprosesor menghasilkan kode yang setara dengan ini (indentasi dan kurung kurawal ditambahkan untuk menekankan arti sebenarnya):
Upaya makro selanjutnya mungkin:
Dan fragmen kode yang sama sekarang menghasilkan:
Dan
else
sekarang kesalahan sintaks. Thedo { ... } while(0)
Menghindari lingkaran kedua masalah ini.Ada satu cara lain untuk menulis makro yang mungkin berfungsi:
Ini meninggalkan fragmen program yang ditampilkan sebagai valid. Para
(void)
pemain mencegahnya digunakan dalam konteks di mana nilai diperlukan - tetapi itu dapat digunakan sebagai operan kiri dari operator koma di manado { ... } while (0)
versi tidak bisa. Jika Anda berpikir Anda harus dapat menanamkan kode debug ke dalam ekspresi seperti itu, Anda mungkin lebih suka ini. Jika Anda lebih suka mewajibkan cetak debug untuk bertindak sebagai pernyataan lengkap, makado { ... } while (0)
versinya lebih baik. Perhatikan bahwa jika tubuh makro melibatkan semi-titik dua (secara kasar), maka Anda hanya dapat menggunakando { ... } while(0)
notasi. Selalu berhasil; mekanisme pernyataan ekspresi bisa lebih sulit untuk diterapkan. Anda mungkin juga mendapatkan peringatan dari kompiler dengan formulir ekspresi yang Anda ingin hindari; itu akan tergantung pada kompiler dan flag yang Anda gunakan.TPOP sebelumnya di http://plan9.bell-labs.com/cm/cs/tpop dan http://cm.bell-labs.com/cm/cs/tpop tetapi keduanya sekarang (2015-08-10) rusak.
Kode di GitHub
Jika Anda penasaran, Anda dapat melihat kode ini di GitHub di repositori SOQ (Stack Overflow Questions) saya sebagai file
debug.c
,debug.h
danmddebug.c
di sub-direktori src / libsoq .sumber
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
, itu tidak akan dikompilasi jika Anda tidak memiliki parameter printf, yaitu jika Anda hanya menelepondebug_print("Some msg\n");
Anda dapat memperbaikinya dengan menggunakanfprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
The ## __ VA_ARGS__ memungkinkan melewati tidak ada parameter ke fungsi.#define debug_print(fmt, ...)
dan#define debug_print(...)
. Yang pertama membutuhkan setidaknya satu argumen, format string (fmt
) dan nol atau lebih argumen lainnya; yang kedua membutuhkan nol atau lebih argumen secara total. Jika Anda menggunakandebug_print()
yang pertama, Anda mendapatkan kesalahan dari preprocessor tentang menyalahgunakan makro, sedangkan yang kedua tidak. Namun, Anda masih mendapatkan kesalahan kompilasi karena teks pengganti tidak valid C. Jadi, sebenarnya tidak banyak perbedaan - karenanya penggunaan istilah 'pengecekan terbatas'.-D input=4,macros=9,rules=2
untuk mengatur level debug dari sistem input ke 4, sistem makro ke 9 (menjalani pengawasan ketat) ) dan sistem aturan ke 2. Ada variasi yang tak ada habisnya pada tema; gunakan apa pun yang cocok untukmu.Saya menggunakan sesuatu seperti ini:
Daripada saya hanya menggunakan D sebagai awalan:
Compiler melihat kode debug, tidak ada masalah koma dan bekerja di mana-mana. Juga berfungsi ketika
printf
tidak cukup, katakanlah ketika Anda harus membuang array atau menghitung beberapa nilai diagnosa yang berlebihan untuk program itu sendiri.EDIT: Ok, ini mungkin menghasilkan masalah ketika ada
else
suatu tempat dekat yang dapat dicegat oleh suntikan iniif
. Ini adalah versi yang membahasnya:sumber
for(;0;)
, mungkin menghasilkan masalah ketika Anda menulis sesuatu sepertiD continue;
atauD break;
.Untuk implementasi portabel (ISO C90), Anda bisa menggunakan tanda kurung ganda, seperti ini;
atau (retas, tidak akan merekomendasikannya)
sumber
Inilah versi yang saya gunakan:
sumber
Saya akan melakukan sesuatu seperti
Saya pikir ini lebih bersih.
sumber
assert()
dari stdlib bekerja dengan cara yang sama dan saya biasanya hanya kembali menggunakanNDEBUG
makro untuk kode debug saya sendiri ...Menurut http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , harus ada
##
sebelumnya__VA_ARGS__
.Jika tidak, makro
#define dbg_print(format, ...) printf(format, __VA_ARGS__)
tidak akan mengkompilasi contoh berikut:dbg_print("hello world");
.sumber
sumber
Inilah yang saya gunakan:
Ini memiliki manfaat yang bagus untuk menangani printf dengan benar, bahkan tanpa argumen tambahan. Dalam kasus DBG == 0, bahkan kompiler paling bodoh tidak dapat mengunyah, jadi tidak ada kode yang dihasilkan.
sumber
Favorit saya di bawah ini adalah
var_dump
, yang ketika disebut sebagai:var_dump("%d", count);
menghasilkan output seperti:
patch.c:150:main(): count = 0
Kredit ke @ "Jonathan Leffler". Semua C89-senang:
Kode
sumber
Jadi, saat menggunakan gcc, saya suka:
Karena itu bisa dimasukkan ke dalam kode.
Misalkan Anda mencoba melakukan debug
Kemudian Anda dapat mengubahnya ke:
Dan Anda bisa mendapatkan analisis ekspresi apa yang dievaluasi untuk apa.
Ini dilindungi terhadap masalah evaluasi ganda, tetapi tidak adanya gensim membiarkannya terbuka untuk tabrakan nama.
Namun ia bersarang:
Jadi saya pikir selama Anda menghindari menggunakan g2rE3 sebagai nama variabel, Anda akan baik-baik saja.
Tentu saja saya telah menemukannya (dan versi sekutu untuk string, dan versi untuk level debug dll) sangat berharga.
sumber
Saya telah memikirkan cara melakukan ini selama bertahun-tahun, dan akhirnya menemukan solusi. Namun, saya tidak tahu bahwa sudah ada solusi lain di sini. Pertama, berbeda dengan jawaban Leffler , saya tidak melihat argumennya bahwa cetakan debug harus selalu dikompilasi. Saya lebih suka tidak memiliki banyak kode yang tidak perlu dieksekusi di proyek saya, ketika tidak diperlukan, dalam kasus di mana saya perlu menguji dan mereka mungkin tidak dioptimalkan.
Tidak mengkompilasi setiap kali mungkin terdengar lebih buruk daripada dalam praktik sebenarnya. Anda benar-benar berakhir dengan cetakan debug yang kadang-kadang tidak dikompilasi, tetapi tidak terlalu sulit untuk mengkompilasi dan mengujinya sebelum menyelesaikan proyek. Dengan sistem ini, jika Anda menggunakan tiga level debug, letakkan saja di pesan debug level tiga, perbaiki kesalahan kompilasi Anda dan periksa yang lainnya sebelum Anda menyelesaikan kode Anda. (Karena tentu saja, penyusunan laporan debug bukan jaminan bahwa mereka masih berfungsi sebagaimana dimaksud.)
Solusi saya juga menyediakan level detail debug; dan jika Anda mengaturnya ke level tertinggi, semuanya dikompilasi. Jika Anda telah menggunakan level detail debug tinggi baru-baru ini, mereka semua dapat melakukan kompilasi pada saat itu. Pembaruan akhir harus cukup mudah. Saya tidak pernah membutuhkan lebih dari tiga level, tetapi Jonathan mengatakan dia menggunakan sembilan level. Metode ini (seperti Leffler) dapat diperluas ke sejumlah level. Penggunaan metode saya mungkin lebih sederhana; hanya membutuhkan dua pernyataan saat digunakan dalam kode Anda. Saya, bagaimanapun, mengkode makro TUTUP juga - meskipun tidak melakukan apa-apa. Mungkin jika saya mengirim ke file.
Terhadap biaya langkah ekstra menguji mereka untuk melihat bahwa mereka akan dikompilasi sebelum pengiriman, adalah itu
Cabang sebenarnya relatif cukup mahal dalam prosesor pre-fetching modern. Mungkin bukan masalah besar jika aplikasi Anda tidak kritis terhadap waktu; tetapi jika kinerja merupakan masalah, maka, ya, masalah yang cukup besar yang saya lebih suka untuk memilih kode debug yang agak lebih cepat dieksekusi (dan mungkin rilis lebih cepat, dalam kasus yang jarang, seperti yang disebutkan).
Jadi, yang saya inginkan adalah makro cetak debug yang tidak mengkompilasi jika tidak akan dicetak, tetapi tidak jika itu dicetak. Saya juga ingin level debugging, jadi, misalnya jika saya ingin bagian kode yang sangat penting kinerja tidak untuk mencetak pada beberapa waktu, tetapi untuk mencetak pada yang lain, saya bisa mengatur level debug, dan meminta tambahan cetakan debug. Saya menemukan cara untuk menerapkan level debug yang menentukan apakah cetakan bahkan dikompilasi atau tidak. Saya mencapainya dengan cara ini:
DebugLog.h:
DebugLog.cpp:
Menggunakan makro
Untuk menggunakannya, lakukan saja:
Untuk menulis ke file log, cukup lakukan:
Untuk menutupnya, Anda harus:
walaupun saat ini ini bahkan tidak perlu, secara teknis, karena tidak melakukan apa-apa. Saya masih menggunakan TUTUP sekarang, namun, jika saya berubah pikiran tentang cara kerjanya, dan ingin membiarkan file terbuka di antara pernyataan logging.
Kemudian, ketika Anda ingin mengaktifkan pencetakan debug, cukup edit #define pertama dalam file header untuk mengatakan, misalnya
Agar laporan logging tidak dikompilasi, lakukan
Jika Anda memerlukan info dari sepotong kode yang sering dieksekusi (yaitu detail tingkat tinggi), Anda mungkin ingin menulis:
Jika Anda mendefinisikan DEBUG menjadi 3, masuk ke kompilasi level 1, 2 & 3. Jika Anda mengaturnya ke 2, Anda mendapatkan logging level 1 & 2. Jika Anda mengaturnya ke 1, Anda hanya mendapatkan pernyataan logging level 1.
Mengenai loop do-while, karena ini mengevaluasi salah satu fungsi tunggal atau tidak sama sekali, alih-alih pernyataan if, loop tidak diperlukan. OK, kata saya untuk menggunakan C daripada C ++ IO (dan QString :: arg () Qt adalah cara yang lebih aman untuk memformat variabel ketika di Qt, juga - itu cukup apik, tetapi membutuhkan lebih banyak kode dan dokumentasi pemformatan tidak terorganisir mungkin - tetapi masih saya telah menemukan kasus-kasus yang lebih disukai), tetapi Anda dapat memasukkan kode apa pun dalam file .cpp yang Anda inginkan. Ini juga mungkin sebuah kelas, tetapi kemudian Anda harus membuat instance dan mengikutinya, atau melakukan yang baru () dan menyimpannya. Dengan cara ini, Anda cukup memasukkan #include, init, dan secara opsional menutup pernyataan ke sumber Anda, dan Anda siap untuk mulai menggunakannya. Akan tetapi, itu akan membuat kelas yang bagus, jika Anda cenderung.
Saya sebelumnya telah melihat banyak solusi, tetapi tidak ada yang cocok dengan kriteria saya dan juga yang ini.
Tidak terlalu signifikan, tetapi juga:
DEBUGLOG_LOG(3, "got here!");
); sehingga memungkinkan Anda untuk menggunakan, misalnya pemformatan Qt .arg () yang lebih aman. Ini bekerja pada MSVC, dan karenanya, mungkin gcc. Ini digunakan##
dalam#define
s, yang non-standar, seperti yang ditunjukkan Leffler, tetapi didukung secara luas. (Anda dapat mengode ulang untuk tidak digunakan##
jika perlu, tetapi Anda harus menggunakan peretasan seperti yang disediakannya.)Peringatan: Jika Anda lupa untuk memberikan argumen tingkat pencatatan, MSVC tidak mengklaim klaim pengidentifikasi tidak didefinisikan.
Anda mungkin ingin menggunakan nama simbol preprosesor selain DEBUG, karena beberapa sumber juga mendefinisikan simbol itu (mis. Prog menggunakan
./configure
perintah untuk mempersiapkan bangunan). Rasanya alami bagi saya ketika saya mengembangkannya. Saya mengembangkannya dalam aplikasi di mana DLL sedang digunakan oleh sesuatu yang lain, dan lebih baik untuk mengirim cetakan log ke file; tetapi mengubahnya ke vprintf () akan bekerja dengan baik juga.Saya harap ini menyelamatkan banyak dari Anda yang berduka karena mencari tahu cara terbaik untuk melakukan debug logging; atau menunjukkan satu yang Anda sukai. Dengan setengah hati aku berusaha memikirkan hal ini selama beberapa dekade. Bekerja di MSVC 2012 & 2015, dan karenanya mungkin pada gcc; dan mungkin bekerja pada banyak orang lain, tetapi saya belum mengujinya pada mereka.
Saya bermaksud membuat versi streaming untuk satu hari ini juga.
Catatan: Terima kasih kepada Leffler, yang dengan ramah membantu saya memformat pesan saya lebih baik untuk StackOverflow.
sumber
if (DEBUG)
pernyataan saat runtime, yang tidak dioptimalkan" - yang miring pada kincir angin . Inti dari sistem yang saya jelaskan adalah bahwa kode tersebut akan diperiksa oleh kompilator (penting, dan otomatis - tidak ada membangun khusus yang dibutuhkan) tetapi kode debug tidak dihasilkan sama sekali karena yang dioptimalkan keluar (sehingga ada nol runtime dampak pada ukuran atau kinerja kode karena kode tidak ada pada saat runtime).((void)0)
- mudah.Saya percaya variasi tema ini memberikan kategori debug tanpa perlu memiliki nama makro yang terpisah per kategori.
Saya menggunakan variasi ini dalam proyek Arduino di mana ruang program dibatasi hingga 32K dan memori dinamis terbatas pada 2K. Penambahan pernyataan debug dan jejak string debug dengan cepat menghabiskan ruang. Jadi sangat penting untuk dapat membatasi jejak debug yang disertakan pada waktu kompilasi ke minimum yang diperlukan setiap kali kode dibangun.
debug.h
memanggil file .cpp
sumber