SORU
29 EKİM 2009, PERŞEMBE


C #hata ayıklama yazdırma için makro tanımlamak

Hata AYIKLAMA, aşağıdaki sahte kod gibi tanımlandığında baskı hata ayıklama iletileri için kullanılabilecek bir makro oluşturmak için çalışıyoruz:

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

Bu nasıl bir makro ile gerçekleştirilir?

CEVAP
29 EKİM 2009, PERŞEMBE


Eğer C99 bir derleyici kullanırsanız

#define debug_print(fmt, ...) \
            do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)

C99 (liste gösterimi önceki sürümlerinde desteklenmiyor değişken bağımsız değişken) kullandığınızı varsayar. do { ... } while (0) deyim kodu açıklama (işlev çağrısı) gibi davranır sağlar. Kodu koşulsuz kullanın derleyici her zaman hata ayıklama kodunuzu geçerli-dir; ancak doktoru DEBUG 0 olduğunda kod kaldıracak kontrol etmesini sağlar.

Eğer #ifdef DEBUG ile çalışmak istiyorsanız, o zaman test durumunu değiştirmek:

#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif

Ve sonra hata AYIKLAMA kullandığım yer DEBUG_TEST kullanın.

Madem ısrar ettiniz bir dize biçimi string (muhtemelen iyi bir fikir zaten), ya da tanıtmak gibi şeyler __FILE__, __LINE__ __func__ çıkış halinde, hangi artırabilir teşhis:

#define debug_print(fmt, ...) \
        do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
                                __LINE__, __func__, __VA_ARGS__); } while (0)

Bu dize birleştirme üzerinde programcı yazar daha büyük bir biçim dizesi oluşturmak için kullanır.

Eğer C89 bir derleyici kullanırsanız

Eğer C89 ve yararlı derleyici uzantısı ile sıkışmış iseniz, o zaman bunu işlemek için özellikle temiz bir yolu yoktur. Ben kullandığı tekniği:

#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)

Ve sonra, kodu, yazma:

TRACE(("message %d\n", var));

Çift parantez çok önemli-ve makro genişleme komik bir gösterim var. Daha önce olduğu gibi, derleyici her zaman sözdizimsel geçerliliği iyi olan bir kodu kontrol eder ancak doktoru eğer hata AYIKLAMA makro sıfır olarak değerlendirilirse yalnızca yazdırma işlevini çağırır.

Bu fonksiyonu desteği-dbg_printf gerektirebilir() örnek& -; gibi şeyler işlemek için''. stderr Sen varargs fonksiyonlar yazmak için bilmek gerekir, ama o kadar da zor değil:

#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);
}

Ayrıca tabii ki C99, bu tekniği kullanabilirsiniz, ama __VA_ARGS__ tekniği normal işlev gösterimde kullandığı için, çift parantez hack değil kıvrımlara.

Neden derleyici her zaman hata ayıklama kodunu görmek çok önemli.

[Ne yorum başka bir cevap için yaptı.]

Hem C89 ve C99 uygulamalarının arkasında bir ana fikir üstünde derleyici doğru her zaman hata ayıklama basit bir beşgen kullanmaktır-gibi ifadeler görmesidir. Bu uzun vadeli bir kod-son kod on yıl ya da iki için önemlidir.

Kod parçası yıllardır çoğunlukla uyuyan () istikrarlı olmuştur, ama şimdi değiştirilmesi gerekiyor sanırım. Yeniden etkinleştirmek için hata ayıklama izleme - ama sinir bozucu yeniden adlandırıldı veya retyped bu değişkenler içerdiği için (izleme) hata ayıklama kod hata ayıklamak için, istikrarlı bakım yıllarında. Eğer derleyici (pre-processor sonrası) her zaman baskı deyimi görürse, herhangi bir çevre değişiklikleri teşhis geçersiz sağlar. Eğer derleyici baskı ifadeyi görüyor mu yoksa kendi dikkatsizlik (veya çalışma arkadaşlarınız ya da ortak dikkatsizlik) karşı korumak edemiyor. 'The Practice of Programming' açıklama satırı yazmak ve Pike, özellikle Bölüm 8 (TPOP ayrıca Wikipedia).

Bu daha önce yapıldı' deneyim somon somon kullandım aslında tekniği açıklanan diğer cevaplar burada olmayan hata ayıklama oluşturmak değil bakın, basit bir beşgen kullanmaktır-gibi ifadeler bir sayının yıl en fazla on yıl). Ama TPOP (benim önceki yorum) tavsiye rastladım, ve daha sonra birkaç yıl sonra bazı hata ayıklama kodu etkinleştirin ve değişen bağlam sorunlar hata ayıklama kırma koştu. Birkaç kez, baskı her zaman geçerliliği olan sorunları daha sonra beni kurtardı.

İddialar sadece kontrol etmek için BU kullanıyorum ve hata ayıklama izleme olup olmadığını kontrol etmek için ayrı bir makro (genellikle hata AYIKLAMA) programı içine inşa edilmiştir. Hatta zaman hata ayıklama izleme dahili, ben sık sık yapmak istemiyor hata ayıklama çıktısını almak için görünür kayıtsız şartsız var mekanizmasını kontrol edip çıktıyı görüntülenir (hata ayıklama düzeyleri, ve yerine çağırıyor fprintf() doğrudan, diyorum bir hata ayıklama yazdırma işlevi yalnızca koşullu olarak yazdırır yani aynı yapı kodu yazdırma veya baskı tabanlı program seçenekleri). Ben de bir 'çoklu-alt' programının farklı bölümleri olabilir, böylece daha büyük programlar için kodu sürüm izleme farklı miktarlarda - zamanı kontrol altında üretiyor.

Tüm yapılar için, derleyici tanı ifadeleri görmek gerektiğini savunan değilim; ancak, derleyici hata ayıklama etkin değilse, hata ayıklama izleme raporları için herhangi bir kod üretmek değil. Temelde, tüm kodunuzu sen - ister serbest bırakılması için derleme derleyici tarafından her zaman kontrol ya da hata ayıklama demektir. Bu iyi bir şeydir!

hata ayıklama.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 */

hata ayıklama.h - 3.6 sürümü (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 */

Tek argüman C99 varyant

Kyle sordu Brandt:

Zaten bu yüzden debug_print hala eğer argümanlar varsa bile çalışır mı? Örneğin:

    debug_print("Foo");

Basit, eski moda bir gardiyan var:

debug_print("%s\n", "Foo");

GCC-tek çözüm de bu desteği sağlar.

Ancak, kullanarak düz C99 sistemi ile yapabilecekleriniz:

#define debug_print(...) \
            do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)

İlk sürümü ile karşılaştırıldığında gerektiren sınırlı kontrolü kaybedersiniz '' birini aramak anlamına gelir değişkeni '() debug_print' argüman. fmt Olup olmadığını kontrol kaybı hiç de tartışmalı bir sorundur.

GCC-belirli bir Teknik

Bazı derleyicileri değişken uzunlukta makro bağımsız değişkeni listeler işleme diğer yolları uzantıları sunabilir. İlk normalde son sonra görünen virgül atlamak için izin verir GCC Hugo Ideler ile yorum '' makro değişken. sabit belirtildiği gibi, özellikle, Ayrıca virgül varsa gösterimde önceki siler makro yeni metni, ##__VA_ARGS__ kullanmak için izin verir, ama sadece, önceki token virgül

#define debug_print(...) \
            do { if (DEBUG) fprintf(stderr, ##__VA_ARGS__); } while (0)

Bu çözüm ilk sürümü yararları korur.


Neden do-while döngüsünden?

do while burada amacı nedir?

Noktalı virgül tarafından takip edilecektir anlamına gelen bir işlev çağrısı gibi görünüyor yani makroyu kullanmak mümkün olmak istiyorum. Bu nedenle, uygun makro vücut paketi var. Eğer do { ... } while (0), çevredeki olmadan if bir ifade kullanırsanız gerekir:

/* 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 basit kod bu eşdeğer (girintili ve parantez gerçek anlamını vurgulamak için eklendi) üretir çünkü akış gerçek kontrol yansıtmak değil:

if (x > y)
{
    if (DEBUG)
        fprintf(stderr, "x (%d) > y (%d)\n", x, y);
    else
        do_something_useful(x, y);
}

Makro bir sonraki girişimi olabilir:

/* BAD - BAD - BAD */
#define debug_print(...) \
            if (DEBUG) { fprintf(stderr, __VA_ARGS__); }

Ve aynı kod parçası artık ü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ı. do { ... } while(0) döngü hem bu sorunları önler.

İşe yarayabilir: makro yazmanın bir başka yolu yok

/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

Bu program parçası geçerli olarak gösterilen bırakır. (void) dökme bir değer gerekli-bulunduğu bağlamlarda kullanıldığını engeller; ama do { ... } while (0) sürüm olmadığı durumlarda virgül operatörünün sol işlenen olarak kullanılabilir. Eğer böyle ifadeler hata kodu gömmek gerekir düşünüyorsanız, bu tercih edebilirsiniz. Eğer hata ayıklama baskı tam bir ifadesi olarak hareket etmek gerektir tercih ediyorsanız, o zaman do { ... } while (0) sürüm daha iyi. Eğer makro vücut noktalı virgül (kabaca söylersek) dahil ise, o zaman sadece do { ... } while(0) gösterimini kullanabilirsiniz. Her zaman işe yarar; ifade deyimi mekanizması uygulamak için daha zor olabilir. Ayrıca kaçmak istersin bu ifade form ile derleyici uyarı alabilirsiniz; derleyici ve kullandığınız bayrakları bağlıdır.


TPOP daha önce http://plan9.bell-labs.com/cm/cs/tpop ve http://cm.bell-labs.com/cm/cs/tpop idi ama şimdi (2015-08-10) kırık.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • HuskyStarcraft

    HuskyStarcra

    4 HAZİRAN 2009
  • J Medema

    J Medema

    11 EKİM 2006
  • Matt Steffanina

    Matt Steffan

    1 EYLÜL 2011