Jika Anda menggunakan kompiler C99 atau yang lebih baru
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
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:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
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:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
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:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
Dan kemudian, dalam kode, tulis:
TRACE(("message %d\n", var));
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:
#include <stdarg.h>
#include <stdio.h>
void dbg_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
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)
/*
@(#)File: $RCSfile: debug.h,v $
@(#)Version: $Revision: 1.2 $
@(#)Last changed: $Date: 1990/05/01 12:55:39 $
@(#)Purpose: Definitions for the debugging system
@(#)Author: J Leffler
*/
#ifndef DEBUG_H
#define DEBUG_H
/* -- Macro Definitions */
#ifdef DEBUG
#define TRACE(x) db_print x
#else
#define TRACE(x)
#endif /* DEBUG */
/* -- Declarations */
#ifdef DEBUG
extern int debug;
#endif
#endif /* DEBUG_H */
debug.h - versi 3.6 (2008-02-11)
/*
@(#)File: $RCSfile: debug.h,v $
@(#)Version: $Revision: 3.6 $
@(#)Last changed: $Date: 2008/02/11 06:46:37 $
@(#)Purpose: Definitions for the debugging system
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 1990-93,1997-99,2003,2005,2008
@(#)Product: :PRODUCT:
*/
#ifndef DEBUG_H
#define DEBUG_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
/*
** Usage: TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x) db_print x
#else
#define TRACE(x) do { if (0) db_print x; } while (0)
#endif /* DEBUG */
#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */
#include <stdio.h>
extern int db_getdebug(void);
extern int db_newindent(void);
extern int db_oldindent(void);
extern int db_setdebug(int level);
extern int db_setindent(int i);
extern void db_print(int level, const char *fmt,...);
extern void db_setfilename(const char *fn);
extern void db_setfileptr(FILE *fp);
extern FILE *db_getfileptr(void);
/* Semi-private function */
extern const char *db_indent(void);
/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/
/*
** Usage: MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x) db_mdprint x
#else
#define MDTRACE(x) do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */
extern int db_mdgetdebug(int subsys);
extern int db_mdparsearg(char *arg);
extern int db_mdsetdebug(int subsys, int level);
extern void db_mdprint(int subsys, int level, const char *fmt,...);
extern void db_mdsubsysnames(char const * const *names);
#endif /* DEBUG_H */
Varian argumen tunggal untuk C99 atau lebih baru
Kyle Brandt bertanya:
Pokoknya untuk melakukan ini debug_print
tetap bekerja meskipun tidak ada argumen? Sebagai contoh:
debug_print("Foo");
Ada satu hack sederhana dan kuno:
debug_print("%s\n", "Foo");
Solusi khusus-GCC yang ditunjukkan di bawah ini juga menyediakan dukungan untuk itu.
Namun, Anda dapat melakukannya dengan sistem C99 langsung dengan menggunakan:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
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:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
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?
Apa tujuan dari do while
disini?
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 sekitarnya do { ... } while (0)
, Anda akan memiliki:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Sekarang, anggaplah Anda menulis:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
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):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
Upaya makro selanjutnya mungkin:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
Dan fragmen kode yang sama sekarang menghasilkan:
if (x > y)
if (DEBUG)
{
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
}
; // Null statement from semi-colon after macro
else
do_something_useful(x, y);
Dan else
sekarang kesalahan sintaks. The do { ... } while(0)
Menghindari lingkaran kedua masalah ini.
Ada satu cara lain untuk menulis makro yang mungkin berfungsi:
/* BAD - BAD - BAD */
#define debug_print(...) \
((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
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 mana do { ... } 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, maka do { ... } while (0)
versinya lebih baik. Perhatikan bahwa jika tubuh makro melibatkan semi-titik dua (secara kasar), maka Anda hanya dapat menggunakan do { ... } 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
dan mddebug.c
di
sub-direktori src / libsoq .