C99 veya daha yeni bir derleyici kullanıyorsanız
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
C99 kullandığınızı varsayar (değişken argüman listesi gösterimi önceki sürümlerde desteklenmez). do { ... } while (0)
Deyim olmasını sağlar kod beyanı (işlev çağrısı) işlevi gören bu. Kodun koşulsuz kullanımı, derleyicinin her zaman hata ayıklama kodunuzun geçerli olup olmadığını kontrol etmesini sağlar - ancak DEBUG 0 olduğunda optimize edici kodu kaldıracaktır.
#İfdef DEBUG ile çalışmak istiyorsanız, test koşulunu değiştirin:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
Ve sonra DEBUG kullandığım yerde DEBUG_TEST kullanın.
Eğer biçim dizesi (yine muhtemelen iyi bir fikir), ayrıca gibi şeyler tanıtabilirsiniz bir dize ısrar ederse __FILE__
, __LINE__
ve __func__
teşhis artırabilir çıkışı, içine:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
Bu, programcının yazdığından daha büyük bir biçim dizesi oluşturmak için dize birleştirmeye dayanır.
Bir C89 derleyici kullanıyorsanız
C89 ile sıkışıp kalırsanız ve yararlı bir derleyici uzantısı yoksa, işlemek için özellikle temiz bir yol yoktur. Eskiden kullandığım teknik:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
Ve sonra, kodda şunu yazın:
TRACE(("message %d\n", var));
Çift parantezler çok önemlidir - ve bu yüzden makro genişlemede komik gösterime sahipsiniz. Daha önce olduğu gibi, derleyici her zaman kodu sözdizimsel geçerlilik açısından kontrol eder (ki bu iyidir), ancak optimize edici yalnızca DEBUG makrosu sıfırdan farklı olarak değerlendirilirse yazdırma işlevini çağırır.
Bu, 'stderr' gibi şeyleri işlemek için örnekte dbg_printf () destek işlevini gerektirir. Varargs işlevlerini nasıl yazacağınızı bilmenizi gerektirir, ancak bu zor değildir:
#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);
}
Bu tekniği elbette C99'da da kullanabilirsiniz, ancak __VA_ARGS__
teknik daha temizdir, çünkü çift parantez kesmek yerine düzenli işlev gösterimi kullanır.
Derleyicinin her zaman hata ayıklama kodunu görmesi neden önemlidir?
[ Başka bir cevaba yapılan yorumları yeniden şekillendirme. ]
Yukarıdaki C99 ve C89 uygulamalarının ardındaki temel fikirlerden biri derleyicinin her zaman hata ayıklama printf benzeri ifadeleri görmesidir. Bu, uzun vadeli kod için önemlidir - on veya iki yıl sürecek kod.
Bir kod parçasının birkaç yıldır çoğunlukla hareketsiz (sabit) olduğunu, ancak şimdi değiştirilmesi gerektiğini varsayalım. Hata ayıklama izini yeniden etkinleştirirsiniz - ancak kararlı bakım yıllarında yeniden adlandırılan veya yeniden yazılan değişkenlere atıfta bulunduğu için hata ayıklama (izleme) kodunda hata ayıklamak zorunda kalırsınız. Derleyici (ön işlemci sonrası) her zaman print deyimini görürse, çevresindeki değişikliklerin tanılamayı geçersiz kılmadığından emin olur. Derleyici baskı ifadesini görmezse, sizi kendi dikkatsizliğinize (ya da iş arkadaşlarınızın veya ortak çalışanlarınızın dikkatsizliğine) karşı koruyamaz. 'Bkz Programlama Uygulama Kernighan ve özellikle Pike, Bölüm 8 tarafından' (Wikipedia ayrıca bkz TPOP ).
Bu 'orada,' yapıldı 'deneyimi - Aslında hata ayıklama yapısının birkaç yıl boyunca (on yıldan fazla) printf benzeri ifadeleri görmediği diğer cevaplarda açıklanan tekniği kullandım. Ancak TPOP'taki tavsiyeye rastladım (önceki yorumuma bakın) ve sonra birkaç yıl sonra bazı hata ayıklama kodunu etkinleştirdim ve hata ayıklamayı bozan değişen bağlam sorunlarıyla karşılaştım. Birkaç kez, baskının her zaman doğrulanması beni daha sonraki sorunlardan kurtardı.
Yalnızca onayları denetlemek için NDEBUG ve hata ayıklama izlemesi programa yerleşik olup olmadığını denetlemek için ayrı bir makro (genellikle DEBUG) kullanın. Hata ayıklama izleme yerleşik olduğunda bile, sık sık hata ayıklama çıktısının koşulsuz görünmesini istemiyorum, bu nedenle çıkışın görünüp görünmeyeceğini denetleyen bir mekanizmaya sahibim (hata ayıklama düzeyleri ve fprintf()
doğrudan çağırmak yerine, yalnızca koşullu olarak yazdırılan bir hata ayıklama yazdırma işlevini çağırıyorum kodun aynı derlemesi program seçeneklerine göre yazdırılabilir veya yazdırılamaz). Ayrıca, daha büyük programlar için kodun 'çoklu alt sistemi' sürümüne sahibim, böylece programın farklı bölümlerini farklı miktarlarda izleme üretebilirim - çalışma zamanı denetimi altında.
Tüm derlemeler için derleyicinin tanı ifadelerini görmesi gerektiğini savunuyorum; ancak, hata ayıklayıcı etkinleştirilmedikçe derleyici hata ayıklama izleme deyimleri için herhangi bir kod oluşturmaz. Temel olarak, her derleme işleminizde, ister sürüm ister hata ayıklama için tüm kodunuzun derleyici tarafından kontrol edildiği anlamına gelir. Bu iyi birşey!
debug.h - sürüm 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 - sürüm 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 */
C99 veya üstü için tek değişkenli değişken
Kyle Brandt sordu:
Yine de bunu yapmak debug_print
için hiçbir argüman olmasa bile hala çalışır? Örneğin:
debug_print("Foo");
Basit, eski moda bir kesmek var:
debug_print("%s\n", "Foo");
Aşağıda gösterilen yalnızca GCC çözümü de bunun için destek sağlar.
Bununla birlikte, düz C99 sistemi ile şunları yapabilirsiniz:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
İlk sürümle karşılaştırıldığında, 'fmt' bağımsız değişkenini gerektiren sınırlı denetimi kaybedersiniz; bu, birinin bağımsız değişken olmadan 'debug_print ()' çağrmayı deneyebileceği anlamına gelir (ancak bağımsız değişken listesindeki arka virgül fprintf()
derlenemez) . Kontrol kaybının bir sorun olup olmadığı tartışmalıdır.
Tek bir argüman için GCC'ye özgü teknik
Bazı derleyiciler, makrolarda değişken uzunluklu bağımsız değişken listelerini işlemenin diğer yolları için uzantılar sunabilir. Özellikle, Hugo Ideler'in yorumlarında ilk kez belirtildiği gibi , GCC, makroya son 'sabit' argümandan sonra normal olarak görünecek virgülü atlamanıza izin verir. Ayrıca ##__VA_ARGS__
, yalnızca önceki belirteç virgül ise, gösterimden önceki virgül silen makro değiştirme metninde kullanmanıza izin verir :
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
Bu çözüm, biçimden sonra isteğe bağlı bağımsız değişkenleri kabul ederken biçim bağımsız değişkenini gerektirme avantajını korur.
Bu teknik aynı zamanda GCC uyumluluğu için Clang tarafından da desteklenmektedir .
Neden do-while döngüsü?
do while
Burada amaç ne ?
Makroyu bir işlev çağrısı gibi görünecek şekilde kullanmak istersiniz, yani noktalı virgül gelir. Bu nedenle, makro gövdesini uygun şekilde paketlemeniz gerekir. Çevresiz bir if
ifade kullanırsanız do { ... } while (0)
, sahip olacaksınız:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Şimdi yazdığınızı varsayalım:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
Ne yazık ki, bu girinti akışın gerçek kontrolünü yansıtmaz, çünkü önişlemci buna eşdeğer kod üretir (girintili ve gerçek anlamı vurgulamak için parantez eklenmiştir):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
Makrodaki bir sonraki deneme şöyle olabilir:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
Ve aynı kod parçası şimdi üretir:
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);
Ve else
şimdi bir sözdizimi hatasıdır. do { ... } while(0)
Döngü önler hem bu sorunları.
Makroyu yazmanın başka bir yolu da olabilir:
/* BAD - BAD - BAD */
#define debug_print(...) \
((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
Bu, program parçasını geçerli olarak bırakır. (void)
Dökme bir değer gereklidir bağlamlarda kullanılan önler - ancak burada bir virgül operatörünün sol işlenen olarak kullanılabilir do { ... } while (0)
versiyonu olamaz. Hata ayıklama kodunu bu tür ifadelere ekleyebilmeniz gerektiğini düşünüyorsanız, bunu tercih edebilirsiniz. Hata ayıklama baskısının tam bir deyim olarak işlev görmesini tercih ediyorsanız, do { ... } while (0)
sürüm daha iyidir. Makronun gövdesinde herhangi bir noktalı virgül varsa (kabaca konuşursak), yalnızca do { ... } while(0)
gösterimi kullanabilirsiniz . Her zaman çalışır; ifade ifadesi mekanizmasının uygulanması daha zor olabilir. Derleyiciden, kaçınmayı tercih ettiğiniz ifade formuyla da uyarılar alabilirsiniz; derleyiciye ve kullandığınız bayraklara bağlı olacaktır.
TPOP daha önce http://plan9.bell-labs.com/cm/cs/tpop ve http://cm.bell-labs.com/cm/cs/tpop adresindeydi ancak ikisi de şimdi (2015-08-10) kırık.
GitHub'daki kod
Konum merak, bu benim de GitHub'dan kod bakabilirsiniz SOQ dosyaları olarak (yığın taşması Sorular) depo debug.c
, debug.h
ve mddebug.c
içinde
src / libsoq
alt dizininde.