#define C hata ayıklama yazdırma makro?


209

DEBUG tanımlandığında, aşağıdaki sahte kod gibi hata ayıklama iletileri yazdırmak için kullanılabilecek bir makro oluşturmaya çalışmak:

#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)

Bu bir makro ile nasıl başarılır?


Derleyici (gcc) if (DEBUG) {...} out gibi ifadeleri optimize eder mi, eğer üretim kodunda DEBUG makrosu 0 olarak ayarlanır mı? Hata ayıklama ifadelerini derleyiciye görünür bırakmak için iyi nedenler olduğunu anlıyorum, ancak kötü bir his var. -Pat
Pat

1
Ayrıca __VA_ARGS__
jww

Yanıtlar:


410

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_printiç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 whileBurada 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 ififade 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.hve mddebug.ciçinde src / libsoq alt dizininde.


1
Bence GCC ## - gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html yaklaşımının "Tek argüman C99 varyantı" başlığı altında değinmeye değer olduğunu düşünüyorum .
Hugo Ideler

2
Yıllar sonra, ve bu cevap hala tüm internetlerin en yararlı, printk nasıl takma! stdio kullanılamadığından vfprintf çekirdek alanında çalışmaz. Teşekkür ederim! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
kevinf

6
Anahtar kelimelerle ilgili örneğinizde, __FILE__, __LINE__, __func__, __VA_ARGS__printf parametreniz yoksa derlenmeyecektir, yani yalnızca çağırırsanız ## __ VA_ARGS__ işlevine hiçbir parametre iletilmesine izin vererek debug_print("Some msg\n"); bunu düzeltebilirsiniz fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);.
mc_electron

1
@LogicTom: fark arasındadır #define debug_print(fmt, ...)ve #define debug_print(...). Bunlardan ilki en az bir bağımsız değişken, biçim dizesi ( fmt) ve sıfır veya daha fazla bağımsız değişken gerektirir; ikincisi toplamda sıfır veya daha fazla argüman gerektirir. debug_print()Birincisi ile kullanırsanız , önişlemciden makroyu kötüye kullanma konusunda bir hata alırsınız, ikincisi ise kullanmaz. Bununla birlikte, değiştirme metni geçerli C olmadığı için hala derleme hataları alıyorsunuz. Bu nedenle, gerçekten çok fazla bir fark yok - bu nedenle 'sınırlı kontrol' teriminin kullanımı.
Jonathan Leffler

1
Yukarıda gösterilen varyant @ St.Antario, tüm uygulama boyunca tek bir etkin hata ayıklama düzeyi kullanır ve genellikle program çalıştırıldığında hata ayıklama düzeyinin ayarlanmasına izin vermek için komut satırı seçeneklerini kullanırım. Ayrıca, her biri bir ad ve kendi hata ayıklama düzeyi verilen birden fazla farklı alt sistemi tanıyan bir varyantım var, böylece -D input=4,macros=9,rules=2giriş sisteminin hata ayıklama düzeyini 4'e, makro sistemini 9'a (yoğun denetimden geçiyor) ayarlamak için kullanabilirim ) ve kurallar sistemi 2'ye. Tema üzerinde sonsuz varyasyonlar var; size uygun olanı kullanın.
Jonathan Leffler

28

Böyle bir şey kullanıyorum:

#ifdef DEBUG
 #define D if(1) 
#else
 #define D if(0) 
#endif

Ben sadece bir önek olarak D kullanın:

D printf("x=%0.3f\n",x);

Derleyici hata ayıklama kodunu görür, virgül sorunu yoktur ve her yerde çalışır. Ayrıca printf, bir diziyi dökmeniz veya programın kendisine yedek olan bazı tanılama değerlerini hesaplamanız gerektiğinde, yeterli olmadığında da çalışır .

EDIT: Tamam, elseyakın bir yerde bu enjekte tarafından kesilebileceği bir sorun olduğunda if. Bu, üzerinden geçen bir sürümdür:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif

3
Gelince for(;0;), D continue;veya gibi bir şey yazdığınızda bir sorun oluşturabilir D break;.
ACcreator

1
Beni yakaladın; bununla birlikte, kazada meydana gelmesi pek olası görünmemektedir.
mbq

11

Taşınabilir (ISO C90) bir uygulama için, bunun gibi çift parantez kullanabilirsiniz;

#include <stdio.h>
#include <stdarg.h>

#ifndef NDEBUG
#  define debug_print(msg) stderr_printf msg
#else
#  define debug_print(msg) (void)0
#endif

void
stderr_printf(const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
}

int
main(int argc, char *argv[])
{
  debug_print(("argv[0] is %s, argc is %d\n", argv[0], argc));
  return 0;
}

veya (hackish, bunu tavsiye etmem)

#include <stdio.h>

#define _ ,
#ifndef NDEBUG
#  define debug_print(msg) fprintf(stderr, msg)
#else
#  define debug_print(msg) (void)0
#endif

int
main(int argc, char *argv[])
{
  debug_print("argv[0] is %s, argc is %d"_ argv[0] _ argc);
  return 0;
}

3
@LB: önişlemcinin sadece bir argüman olduğunu `` düşünmesini '' sağlarken, _'in daha sonraki bir aşamada genişletilmesine izin verir.
Marcin Koziuk

10

İşte kullandığım sürüm:

#ifdef NDEBUG
#define Dprintf(FORMAT, ...) ((void)0)
#define Dputs(MSG) ((void)0)
#else
#define Dprintf(FORMAT, ...) \
    fprintf(stderr, "%s() in %s, line %i: " FORMAT "\n", \
        __func__, __FILE__, __LINE__, __VA_ARGS__)
#define Dputs(MSG) Dprintf("%s", MSG)
#endif

9

Gibi bir şey yapardım

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

Bence bu daha temiz.


Testin içindeki makroyu bayrak olarak kullanma fikrinden pek hoşlanmıyorum. Hata ayıklama baskısının neden her zaman kontrol edilmesi gerektiğini açıklayabilir misiniz?
LB40

1
@Jonathan: Kod yalnızca hata ayıklama modunda yürütülürse, hata ayıklama modunda derlenmesine neden dikkat etmelisiniz? assert()stdlib aynı şekilde çalışır ve normalde sadece NDEBUGkendi hata ayıklama kodumu için makroyu yeniden ...
Christoph

DEBUG kullanarak testte, biri kontrolsüz undef DEBUG yaparsa, kodunuz artık derlenmez. sağ ?
LB40

4
Hata ayıklamayı etkinleştirmek ve daha sonra yeniden adlandırılmış veya yeniden yazılan değişkenleri ifade ettiği için hata ayıklama kodunda hata ayıklamak gerekir. Derleyici (ön işlemci sonrası) her zaman print deyimini görürse, çevredeki değişikliklerin teşhisi geçersiz kılmadı. 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. Kernighan ve Pike tarafından hazırlanan 'Programlama Uygulaması' - plan9.bell-labs.com/cm/cs/tpop .
Jonathan Leffler

1
@Christoph: iyi, bir tür ... Sadece iddiaları kontrol etmek için NDEBUG ve hata ayıklama izlemeyi kontrol etmek için ayrı bir makro (genellikle DEBUG) kullanıyorum. Sık sık hata ayıklama çıktı koşulsuz görünmesini istemiyorum, bu yüzden çıkışın görünüp görünmeyeceğini denetlemek için mekanizma var (hata ayıklama düzeyleri ve doğrudan fprintf () çağırmak yerine, sadece koşullu olarak aynı yapıda yazdırılan bir hata ayıklama yazdırma işlevi çağırıyorum program seçeneklerine göre yazdırabilir veya yazdıramaz). Tüm derlemeler için, derleyicinin tanı ifadelerini görmesi gerektiğini savunuyorum; ancak hata ayıklama etkin olmadığı sürece kod oluşturmaz.
Jonathan Leffler

8

Göre http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , bir olmalı ##önce __VA_ARGS__.

Aksi takdirde, bir makro #define dbg_print(format, ...) printf(format, __VA_ARGS__)aşağıdaki örneği derlemek olmaz: dbg_print("hello world");.


1
Stack Overflow'a hoş geldiniz. GCC'nin başvurduğunuz standart dışı uzantıya sahip olduğundan emin olursunuz. Şu anda kabul edilen cevap, tam olarak verdiğiniz referans URL'si de dahil olmak üzere aslında bunu belirtmektedir.
Jonathan Leffler

7
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)

Hangi C sürümü bu gösterimi destekliyor? Ve eğer işe yaradıysa, jeton tüm argümanları bu şekilde yapıştırmak, biçim dizesi için sadece çok sınırlı bir seçenek kümeniz olduğu anlamına gelir, değil mi?
Jonathan Leffler

@Jonathan: gcc (Debian 4.3.3-13) 4.3.3
eyalm

1
Tamam - kabul edildi: eski bir GNU uzantısı olarak belgelendi (GCC 4.4.1 kılavuzunun 5.17 bölümü). Ama muhtemelen sadece GCC ile çalışacağını belgelemelisiniz - ya da belki de bu yorumlarda aramızda yaptık.
Jonathan Leffler

1
Amacım, argümanları kullanmanın başka bir stilini göstermek ve esas olarak FUNCTION ve LINE
eyalm

2

Ne kullanıyorum:

#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/  
#endif

Ek argümanlar olmadan bile printf'i düzgün bir şekilde ele almanın iyi bir yararı vardır. DBG == 0 olması durumunda, en aptal derleyici bile çiğnemek için hiçbir şey almaz, bu nedenle kod üretilmez.


Derleyicinin her zaman hata ayıklama kodunu kontrol etmesi daha iyidir.
Jonathan Leffler

1

Aşağıdakiler arasından favorim var_dump:

var_dump("%d", count);

gibi çıktılar üretir:

patch.c:150:main(): count = 0

@ "Jonathan Leffler" a teşekkür ederiz. Hepsi C89 mutlu:

kod

#define DEBUG 1
#include <stdarg.h>
#include <stdio.h>
void debug_vprintf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

/* Call as: (DOUBLE PARENTHESES ARE MANDATORY) */
/* var_debug(("outfd = %d, somefailed = %d\n", outfd, somefailed)); */
#define var_debug(x) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf x; }} while (0)

/* var_dump("%s" variable_name); */
#define var_dump(fmt, var) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf ("%s = " fmt, #var, var); }} while (0)

#define DEBUG_HERE do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): HERE\n", \
    __FILE__,  __LINE__, __func__); }} while (0)

1

Yani, gcc kullanırken, beğendim:

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

Çünkü koda eklenebilir.

Hata ayıklamaya çalıştığınızı varsayalım

printf("%i\n", (1*2*3*4*5*6));

720

Ardından şu şekilde değiştirebilirsiniz:

printf("%i\n", DBGI(1*2*3*4*5*6));

hello.c:86:main(): 1*2*3*4*5*6->720
720

Ve hangi ifadenin neye göre değerlendirildiğine dair bir analiz alabilirsiniz.

Çifte değerlendirme sorununa karşı korunur, ancak gensim yokluğu onu isim-çarpışmalara açık bırakır.

Ancak yuva yapar:

DBGI(printf("%i\n", DBGI(1*2*3*4*5*6)));

hello.c:86:main(): 1*2*3*4*5*6->720
720
hello.c:86:main(): printf("%i\n", DBGI(1*2*3*4*5*6))->4

Bu yüzden değişken adı olarak g2rE3 kullanmaktan kaçındığınız sürece sorun olmayacağını düşünüyorum.

Kesinlikle buldum (ve dizeler için müttefik sürümleri ve hata ayıklama düzeyleri için sürümler) paha biçilmez.


1

Bunu yıllardır nasıl yapacağımı araştırıyorum ve sonunda bir çözüm buluyorum. Ancak, burada başka çözümlerin olduğunu bilmiyordum. İlk olarak, Leffler'ın cevabından farklı olarak , hata ayıklama baskılarının her zaman derlenmesi gerektiği iddiasını görmüyorum. Test etmem gereken durumlarda, projemde tonlarca gereksiz kod yürütmeyi tercih etmem ve optimize edilmeyebilirler.

Her seferinde derlemek, gerçekte olduğundan daha kötü gelebilir. Bazen derlenmeyen hata ayıklama baskılarıyla sarılırsınız, ancak bir projeyi sonuçlandırmadan önce bunları derlemek ve test etmek o kadar zor değildir. Bu sistemde, üç hata ayıklama seviyesi kullanıyorsanız, hata ayıklama mesajı 3. seviyeye koyun, derleme hatalarınızı düzeltin ve yer kodunu tamamlamadan önce başkalarını kontrol edin. (Elbette, derleme hata ayıklama ifadeleri hala amaçlandığı gibi çalıştıklarını garanti etmez.)

Benim çözümüm hata ayıklama ayrıntı düzeyleri de sağlar; ve en üst düzeye ayarlarsanız hepsi derlenir. Son zamanlarda yüksek bir hata ayıklama ayrıntı düzeyi kullanıyorsanız, hepsi o zaman derleyebildi. Son güncellemeler oldukça kolay olmalı. Hiç üç seviyeden fazlasına ihtiyacım olmadı, ama Jonathan dokuz tane kullandığını söylüyor. Bu yöntem (Leffler gibi) herhangi bir sayıda seviyeye genişletilebilir. Metodumun kullanımı daha basit olabilir; kodunuzda kullanıldığında yalnızca iki ifade gerektirir. Bununla birlikte, KAPALI makrosunu da kodluyorum - hiçbir şey yapmasa da. Bir dosyaya gönderiyorsam olabilir.

Maliyete karşı, teslimattan önce derleneceklerini test etmenin ekstra adımı,

  1. Optimize edilmeleri için onlara güvenmelisiniz, bu da yeterli bir optimizasyon seviyeniz varsa kabul edilmelidir.
  2. Ayrıca, test amacıyla (optimizasyon nadirdir) kapalı optimizasyon ile bir sürüm derleme yaparsanız muhtemelen olmaz; ve hata ayıklama sırasında neredeyse hiç olmazlar - böylece çalışma zamanında düzinelerce veya yüzlerce "if (DEBUG)" ifadesi çalıştırılır; böylece yürütme (benim ilke itiraz) yavaşlatma ve daha az önemlisi, yürütülebilir veya dll boyutunu artırmak; ve dolayısıyla yürütme ve derleme süreleri. Ancak Jonathan bana ifadeleri derlememe yönteminin yapılabileceğini bildiriyor.

Şubeler aslında modern ön getirme işlemcilerinde nispeten oldukça maliyetlidir. Uygulamanız zaman açısından kritik bir uygulama değilse, büyük bir sorun olmayabilir; ancak performans bir sorunsa, evet, biraz daha hızlı çalışan hata ayıklama kodunu (ve belirtildiği gibi nadir durumlarda daha hızlı yayınlamayı) tercih etmeyi tercih edeceğim yeterince büyük bir anlaşma.

Yani, istediğim, yazdırılmayacaksa derlenmeyen bir hata ayıklama yazdırma makrosu. Ben de hata ayıklama seviyeleri istedim, bu yüzden, örneğin kodun performans açısından önemli kısımlarının bazı zamanlarda yazdırmamasını, ancak başkalarına yazdırmasını istersem, bir hata ayıklama düzeyi ayarlayabilir ve ekstra hata ayıklama baskılarının devreye girmesini sağlayabilirim. baskının derlenip derlenmediğini belirleyen hata ayıklama düzeylerini uygulamanın bir yolunu buldu. Bu şekilde başardım:

DebugLog.h:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  Level 3 is the most information.
// Levels 2 and 1 have progressively more.  Thus, you can write: 
//     DEBUGLOG_LOG(1, "a number=%d", 7);
// and it will be seen if DEBUG is anything other than undefined or zero.  If you write
//     DEBUGLOG_LOG(3, "another number=%d", 15);
// it will only be seen if DEBUG is 3.  When not being displayed, these routines compile
// to NOTHING.  I reject the argument that debug code needs to always be compiled so as to 
// keep it current.  I would rather have a leaner and faster app, and just not be lazy, and 
// maintain debugs as needed.  I don't know if this works with the C preprocessor or not, 
// but the rest of the code is fully C compliant also if it is.

#define DEBUG 1

#ifdef DEBUG
#define DEBUGLOG_INIT(filename) debuglog_init(filename)
#else
#define debuglog_init(...)
#endif

#ifdef DEBUG
#define DEBUGLOG_CLOSE debuglog_close
#else
#define debuglog_close(...)
#endif

#define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__)

#if DEBUG == 0
#define DEBUGLOG_LOG0(...)
#endif

#if DEBUG >= 1
#define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG1(...)
#endif

#if DEBUG >= 2
#define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG2(...)
#endif

#if DEBUG == 3
#define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG3(...)
#endif

void debuglog_init(char *filename);
void debuglog_close(void);
void debuglog_log(char* format, ...);

DebugLog.cpp:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  See DebugLog.h's remarks for more 
// info.

#include <stdio.h>
#include <stdarg.h>

#include "DebugLog.h"

FILE *hndl;
char *savedFilename;

void debuglog_init(char *filename)
{
    savedFilename = filename;
    hndl = fopen(savedFilename, "wt");
    fclose(hndl);
}

void debuglog_close(void)
{
    //fclose(hndl);
}

void debuglog_log(char* format, ...)
{
    hndl = fopen(savedFilename,"at");
    va_list argptr;
    va_start(argptr, format);
    vfprintf(hndl, format, argptr);
    va_end(argptr);
    fputc('\n',hndl);
    fclose(hndl);
}

Makroları kullanma

Kullanmak için şunları yapın:

DEBUGLOG_INIT("afile.log");

Günlük dosyasına yazmak için şunları yapmanız yeterlidir:

DEBUGLOG_LOG(1, "the value is: %d", anint);

Kapatmak için şunları yaparsınız:

DEBUGLOG_CLOSE();

şu anda teknik olarak, hiçbir şey yapmadığı için bu bile gerekli değildir. Hala nasıl çalıştığı hakkında fikrimi değiştirmek ve dosyayı günlük deyimleri arasında açık bırakmak istiyorum, ancak şu anda hala KAPALI kullanıyorum.

Daha sonra, hata ayıklama yazdırmayı açmak istediğinizde, başlık dosyasındaki ilk # tanımlamayı düzenleyin, örn.

#define DEBUG 1

Günlük deyimlerinin hiçbir şey için derlenmesini istemiyorsanız,

#define DEBUG 0

Sık çalıştırılan bir kod parçasından (yani yüksek düzeyde ayrıntıdan) bilgiye ihtiyacınız varsa, şunları yazmak isteyebilirsiniz:

 DEBUGLOG_LOG(3, "the value is: %d", anint);

DEBUG'ı 3 olarak tanımlarsanız, günlük kaydı düzeyleri 1, 2 ve 3 derlenir. 2 olarak ayarlarsanız, günlük düzeyi 1 ve 2'yi alırsınız. 1 olarak ayarlarsanız, yalnızca günlük düzeyi 1 deyimlerini alırsınız.

Do-while döngüsüne gelince, bu bir if ifadesi yerine tek bir işlevi ya da hiçbir şeyi değerlendirmediğinden, döngüye gerek yoktur. Tamam, C ++ IO (ve Qt'un QString :: arg () yerine C'yi kullanmak için beni teşvik edin, Qt'deyken değişkenleri biçimlendirmenin daha güvenli bir yoludur - oldukça şıktır, ancak daha fazla kod alır ve biçimlendirme belgeleri organize değil olabilir - ama yine de tercih edilen durumlarda buldum), ancak istediğiniz .cpp dosyasına istediğiniz kodu koyabilirsiniz. Aynı zamanda bir sınıf da olabilir, ama sonra onu başlatmanız ve ona ayak uydurmanız ya da yeni bir () yapmanız ve saklamanız gerekir. Bu şekilde, #include, init ve isteğe bağlı olarak close ifadelerini kaynağınıza bırakırsınız ve kullanmaya başlamaya hazır olursunuz. Ancak, eğer bu kadar eğimli olursanız, iyi bir sınıf olurdu.

Daha önce birçok çözüm görmüştüm, ama hiçbiri kriterlerime uygun değil.

  1. İstediğiniz kadar seviye yapmak için genişletilebilir.
  2. Yazdırmıyorsa hiçbir şey derlemez.
  3. IO'yu kolay düzenlenebilir bir yerde merkezileştirir.
  4. Printf formatlama kullanarak esnektir.
  5. Yine, her zaman derleyen hata ayıklama baskıları her zaman hata ayıklama modunda yürütülürken, hata ayıklama çalışmalarını yavaşlatmaz. Bilgisayar bilimi yapıyorsanız ve bilgi işlemeyi yazmak daha kolay değilse, örneğin hata ayıklayıcının bir vektör için aralık dışında bir dizinle nerede durduğunu görmek için kendinizi CPU tüketen bir simülatör çalıştırırken bulabilirsiniz. Bunlar zaten hata ayıklama modunda çok yavaş çalışıyor. Yüzlerce hata ayıklama baskısının zorunlu olarak yürütülmesi, bu tür işlemleri daha da yavaşlatacaktır. Benim için, bu tür koşular nadir değildir.

Çok önemli değil, ancak ek olarak:

  1. Bağımsız değişkenler olmadan yazdırmak için kesmek gerekmez (örneğin DEBUGLOG_LOG(3, "got here!");); böylece Qt'nin daha güvenli .arg () biçimlendirmesini kullanmanıza izin verir. MSVC ve muhtemelen gcc üzerinde çalışır. Bu kullanır ##içinde #defineLeffler işaret ettiği gibi, standart dışı bir s, ama geniş bir şekilde desteklenmektedir. ( ##Gerekirse kullanmamak için yeniden kodlayabilirsiniz , ancak sağladığı gibi bir kesmek kullanmanız gerekecektir.)

Uyarı: Günlüğe kaydetme düzeyi bağımsız değişkenini sağlamayı unutursanız, MSVC yararsız olarak tanımlayıcı tanımlanmadığını iddia eder.

Bazı kaynak da bu sembolü tanımladığından, DEBUG dışında bir önişlemci sembol adı kullanmak isteyebilirsiniz (örn. Binaya ./configurehazırlanmak için komutları kullanan progs ). Geliştirdiğimde bana doğal geldi. Ben DLL başka bir şey tarafından kullanılan bir uygulamada geliştirdi ve bir dosyaya günlük baskılar göndermek için daha manastır; ancak vprintf () olarak değiştirmek de işe yarayacaktır.

Umarım bu birçoğunuz hata ayıklama günlüğü yapmanın en iyi yolunu bulmak konusunda kederden tasarruf sağlar; veya tercih edebileceğiniz birini gösterir. On yıllardır bunu içtenlikle anlamaya çalışıyorum. MSVC 2012 ve 2015 ve dolayısıyla muhtemelen gcc'de çalışır; ve muhtemelen başkaları üzerinde çalışıyorum, ama ben onları test etmedim.

Demek istiyorum ki bu bir gün de bir akış versiyonu yapmak.

Not: Mesajımı StackOverflow için daha iyi biçimlendirmeme yardım eden Leffler'e teşekkürler.


2
"Değirmencilikte yüzlerce if (DEBUG)ifadeyi yürütmek, diyorlar ki bu optimize edilmiyor" - yel değirmenlerinde devriliyor . Ben açıklanan sistemin bütün mesele kod derleyici tarafından kontrol edilir olmasıdır - bu, çünkü (önemli ve otomatik özel inşa gerekli) ama ayıklama kodu hiç oluşturulmaz edilir dışarı optimize (yani sıfır zamanı etkisi üzerinde var kod boyutu veya performansı, çünkü kod çalışma zamanında mevcut değildir).
Jonathan Leffler

Jonathan Leffler: Yanlış ifadelerime işaret ettiğin için teşekkürler. Düşüncelerimin parmaklarımdan daha hızlı yarışmasına izin verdim, bunun bittiğinden çok memnunum. İtirazlarımı "... 1) ile optimize ettim, optimize etmek için onlara güvenmelisin, bu da yeterli bir optimizasyon seviyen varsa kuşkusuz gerçekleşmelidir. 2) Ayrıca, optimizasyon ile derleme yaparsan olmaz test amacıyla kapatılmıştır ve muhtemelen hata ayıklama sırasında hiç çalışmazlar - böylece çalışma zamanında düzinelerce veya yüzlerce 'if (DEBUG)' ifadesi çalıştırılır - böylece yürütülebilir veya dll boyutunuzu ve yürütme sürelerinizi arttırırlar.
CodeLurker

Seninki benimkinin yaptığı diğer önemli şeyi yapmak için, hata ayıklama seviyelerine sahip olmalısın. Çoğu zaman açık olan çoğu şeye ihtiyaç duymama rağmen, birkaç uygulama basit bir "#define DEBUG 3" ile zaman açısından kritik bir döngü hakkında büyük bir ayrıntı düzeyi elde etmekten gerçekten yararlanır ve ardından "#define DEBUG 1" ile çok daha az ayrıntılı bilgi. Hiç üç seviyeden daha fazlasına ihtiyacım olmadı ve bu nedenle, hata ayıklarımın en az 1 / 3'ü zaten serbest bırakıldı. Son zamanlarda seviye 3'ü kullandıysam, muhtemelen hepsi yapıyorlar.
CodeLurker

YMMV. Gösterdiğim modern sistem, hata ayıklama düzeylerinin dinamik (çalışma zamanı) ayarını destekler, böylece çalışma zamanında hata ayıklığının ne kadarının üretileceğine programlı olarak karar verebilirsiniz. Üst sınır (veya alt sınır olmamasına rağmen, genellikle 1-9 düzeylerini kullandım; varsayılan düzey genellikle kapalıdır, ancak uygun olduğunda etkin geliştirme sırasında açıkça istenebilir - uzun süreli çalışma için uygun değildir). Ben varsayılan bir seviye 3 seçti; ayarlanabiliyor. Bu bana çok fazla kontrol sağlıyor. Eğer aktif değilken hata ayıklama kodunu gerçekten test etmek istemiyorsanız, alternatifi değiştirin ((void)0)- bu kolaydır.
Jonathan Leffler

1
Ahh. Her şeyi okumak yardımcı olurdu. Oldukça uzun bir yazı. Sanırım şu ana kadar önemli noktalar var. Benimki gibi, tüm hata ayıklama baskılarını derlemek veya derlemek için kullanılabilir ve seviyeleri destekleyebilir; Kuşkusuz, sizinki kullanmadığınız seviyeleri hata ayıklama sırasında bir maliyetle derleyebilir.
CodeLurker

0

Temanın bu varyasyonunun, kategori başına ayrı bir makro adına gerek kalmadan hata ayıklama kategorileri verdiğine inanıyorum.

Bu varyasyonu, program alanının 32K ve dinamik belleğin 2K ile sınırlı olduğu bir Arduino projesinde kullandım. Hata ayıklama deyimlerinin ve izleme hata ayıklama dizelerinin eklenmesi hızlı bir şekilde yer kaplar. Bu nedenle, derleme zamanında dahil edilen hata ayıklama izlemesini, kod her oluşturulduğunda gereken minimum değerle sınırlayabilmeniz önemlidir.

Debug.h'ye

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif

.cpp dosyasını çağırıyor

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.