#define macro สำหรับการดีบักการพิมพ์ใน C?


209

กำลังพยายามสร้างแมโครซึ่งสามารถใช้สำหรับข้อความดีบักการพิมพ์เมื่อกำหนด DEBUG เช่นรหัสเทียมต่อไปนี้:

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

การทำมาโครนี้ทำได้อย่างไร


คอมไพเลอร์ (gcc) จะปรับคำสั่งให้เหมาะสมเช่นถ้า (DEBUG) {... } หมดหรือไม่ถ้าในรหัสการผลิตแมโคร DEBUG ถูกตั้งค่าเป็น 0 ฉันเข้าใจว่ามีเหตุผลที่ดีที่จะปล่อยให้ debug statement ปรากฏต่อคอมไพเลอร์ แต่ความรู้สึกไม่ดียังคงอยู่ -Pat
Pat

คำตอบ:


410

ถ้าคุณใช้คอมไพเลอร์ C99 หรือใหม่กว่า

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

จะถือว่าคุณใช้ C99 (สัญลักษณ์รายการอาร์กิวเมนต์ตัวแปรไม่ได้รับการสนับสนุนในรุ่นก่อนหน้า) do { ... } while (0)สำนวนเพื่อให้แน่ใจว่ารหัสทำหน้าที่เหมือนคำสั่ง (สายงาน) การใช้รหัสอย่างไม่มีเงื่อนไขช่วยให้มั่นใจว่าคอมไพเลอร์ตรวจสอบว่ารหัสการดีบักของคุณถูกต้องเสมอ - แต่เครื่องมือเพิ่มประสิทธิภาพจะลบรหัสเมื่อ DEBUG เป็น 0

หากคุณต้องการทำงานกับ #ifdef DEBUG ให้เปลี่ยนเงื่อนไขการทดสอบ:

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

จากนั้นใช้ DEBUG_TEST ที่ฉันใช้ DEBUG

หากคุณยืนยันในตัวอักษรสตริงสตริงรูปแบบ (อาจจะเป็นความคิดที่ดีอยู่แล้ว) นอกจากนี้คุณยังสามารถแนะนำสิ่งที่ต้องการ__FILE__, __LINE__และ__func__เข้าออกซึ่งสามารถปรับปรุงการวินิจฉัย:

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

สิ่งนี้ขึ้นอยู่กับการต่อสตริงเพื่อสร้างสตริงรูปแบบที่ใหญ่กว่าโปรแกรมเมอร์เขียน

ถ้าคุณใช้คอมไพเลอร์ C89

หากคุณติดอยู่กับ C89 และไม่มีส่วนขยายคอมไพเลอร์ที่มีประโยชน์แสดงว่าไม่มีวิธีที่ชัดเจนในการจัดการกับมัน เทคนิคที่ฉันเคยใช้คือ:

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

จากนั้นในรหัสให้เขียน:

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

วงเล็บสองแผ่นมีความสำคัญและเป็นเหตุผลที่คุณมีสัญกรณ์ตลกในการขยายมาโคร เมื่อก่อนคอมไพเลอร์ตรวจสอบรหัสสำหรับความถูกต้องทางไวยากรณ์ (ซึ่งเป็นสิ่งที่ดี) เสมอ แต่เครื่องมือเพิ่มประสิทธิภาพจะเรียกใช้ฟังก์ชันการพิมพ์เท่านั้นหากแมโคร DEBUG ประเมินว่าไม่ใช่ศูนย์

สิ่งนี้ต้องการฟังก์ชันสนับสนุน - dbg_printf () ในตัวอย่าง - เพื่อจัดการกับสิ่งต่าง ๆ เช่น 'stderr' มันต้องการให้คุณรู้วิธีเขียนฟังก์ชัน varargs แต่นั่นไม่ใช่เรื่องยาก:

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

นอกจากนี้คุณยังสามารถใช้เทคนิคนี้ใน C99 แน่นอน แต่__VA_ARGS__เทคนิคนี้เป็น neater เพราะใช้สัญกรณ์ฟังก์ชั่นปกติไม่ใช่การแฮ็กวงเล็บคู่

ทำไมจึงเป็นสิ่งสำคัญที่คอมไพเลอร์มักจะเห็นรหัสการแก้ปัญหา?

[การแสดงความคิดเห็นใหม่อีกครั้งเป็นคำตอบอื่น ]

แนวคิดหลักหนึ่งที่อยู่เบื้องหลังการใช้งานทั้ง C99 และ C89 ด้านบนคือคอมไพเลอร์ที่เหมาะสมจะเห็นคำสั่งที่เหมือนการพิมพ์ นี่เป็นสิ่งสำคัญสำหรับรหัสระยะยาว - รหัสที่จะคงอยู่ในทศวรรษหรือสองปี

สมมติว่าชิ้นส่วนของรหัสนั้นส่วนใหญ่นิ่ง (เสถียร) เป็นเวลาหลายปี แต่ตอนนี้จำเป็นต้องเปลี่ยน คุณเปิดใช้งานการติดตามการดีบั๊กอีกครั้ง - แต่มันน่าผิดหวังที่จะต้องทำการดีบั๊กรหัสการติดตาม (การติดตาม) เพราะมันหมายถึงตัวแปรที่ถูกเปลี่ยนชื่อหรือพิมพ์ใหม่ในช่วงปีของการบำรุงรักษาที่มั่นคง หากคอมไพเลอร์ (โพสต์โปรเซสเซอร์ล่วงหน้า) เห็นคำสั่งการพิมพ์เสมอจะทำให้มั่นใจได้ว่าการเปลี่ยนแปลงใด ๆ รอบข้างนั้นไม่ได้ทำให้การวินิจฉัยไม่ถูกต้อง หากคอมไพเลอร์ไม่เห็นข้อความสั่งพิมพ์จะไม่สามารถป้องกันคุณจากความประมาทของคุณเอง (หรือความประมาทของเพื่อนร่วมงานหรือผู้ทำงานร่วมกัน) ดู ' การฝึกฝนเขียนโปรแกรม ' โดย Kernighan และ Pike โดยเฉพาะบทที่ 8 (ดู Wikipedia บนTPOP )

นี่คือ 'อยู่ที่นั่นทำแล้ว' ประสบการณ์ - ฉันใช้เป็นหลักเทคนิคที่อธิบายไว้ในคำตอบอื่น ๆ ที่บิลด์ที่ไม่ใช่การดีบักไม่เห็นคำสั่งที่คล้ายกับ printf เป็นเวลาหลายปี (มากกว่าหนึ่งทศวรรษ) แต่ฉันเจอคำแนะนำใน TPOP (ดูความคิดเห็นก่อนหน้าของฉัน) จากนั้นก็เปิดใช้งานรหัสการดีบักหลังจากผ่านไปหลายปีและพบปัญหาบริบทที่เปลี่ยนไปซึ่งทำให้การดีบัก หลายครั้งที่การพิมพ์ที่ผ่านการตรวจสอบแล้วช่วยฉันจากปัญหาในภายหลัง

ฉันใช้ NDEBUG เพื่อควบคุมการยืนยันเท่านั้นและแมโครที่แยกต่างหาก (ปกติคือ DEBUG) เพื่อควบคุมว่าการติดตามการดีบักถูกสร้างขึ้นในโปรแกรมหรือไม่ แม้เมื่อมีการติดตามการดีบักในตัวฉันก็ไม่ต้องการให้ debug output ปรากฏอย่างไม่มีเงื่อนไขดังนั้นฉันจึงมีกลไกในการควบคุมว่าผลลัพธ์จะปรากฏขึ้นหรือไม่ (ระดับการดีบักและแทนที่จะเรียกfprintf()โดยตรงฉันเรียกฟังก์ชัน debug print ที่พิมพ์ตามเงื่อนไขเท่านั้น ดังนั้นการสร้างรหัสเดียวกันสามารถพิมพ์หรือไม่พิมพ์ตามตัวเลือกของโปรแกรม) ฉันยังมีรหัส 'หลายระบบย่อย' สำหรับโปรแกรมที่ใหญ่กว่าเพื่อให้ฉันสามารถมีส่วนต่าง ๆ ของโปรแกรมที่สร้างจำนวนการติดตามที่แตกต่างกัน - ภายใต้การควบคุมแบบรันไทม์

ฉันกำลังสนับสนุนว่าสำหรับการสร้างทั้งหมดคอมไพเลอร์ควรเห็นงบการวินิจฉัย; อย่างไรก็ตามคอมไพเลอร์จะไม่สร้างรหัสใด ๆ สำหรับคำสั่งการติดตามการดีบักจนกว่าจะเปิดใช้งานการดีบัก โดยทั่วไปหมายความว่าโค้ดทั้งหมดของคุณจะถูกตรวจสอบโดยคอมไพเลอร์ทุกครั้งที่คุณคอมไพล์ไม่ว่าจะเป็นการรีลีสหรือการดีบั๊ก นี่เป็นสิ่งที่ดี!

debug.h - รุ่น 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 - รุ่น 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 หรือใหม่กว่า

Kyle Brandt ถาม:

อย่างไรก็ตามการทำเช่นนี้debug_printยังคงใช้ได้แม้ว่าจะไม่มีข้อโต้แย้ง? ตัวอย่างเช่น:

    debug_print("Foo");

มีแฮ็คที่เรียบง่ายและล้าสมัยอย่างหนึ่ง:

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

โซลูชันเฉพาะของ GCC ที่แสดงด้านล่างยังให้การสนับสนุน

อย่างไรก็ตามคุณสามารถทำได้ด้วยระบบตรง C99 โดยใช้:

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

เปรียบเทียบกับรุ่นแรกคุณสูญเสียการตรวจสอบที่ จำกัด ซึ่งต้องใช้อาร์กิวเมนต์ 'fmt' ซึ่งหมายความว่ามีคนพยายามโทร 'debug_print ()' โดยไม่มีข้อโต้แย้ง (แต่เครื่องหมายจุลภาคต่อท้ายในรายการอาร์กิวเมนต์fprintf()จะไม่สามารถรวบรวมได้) . การสูญเสียการตรวจสอบเป็นปัญหาหรือไม่

เทคนิคเฉพาะ GCC สำหรับอาร์กิวเมนต์เดี่ยว

คอมไพเลอร์บางตัวอาจเสนอส่วนขยายสำหรับวิธีอื่นในการจัดการรายการอาร์กิวเมนต์ที่มีความยาวผันแปรได้ในแมโคร โดยเฉพาะอย่างยิ่งตามที่ระบุไว้เป็นครั้งแรกในความคิดเห็นโดยHugo Ideler , GCC ช่วยให้คุณสามารถละเว้นเครื่องหมายจุลภาคที่ปกติจะปรากฏขึ้นหลังจากอาร์กิวเมนต์ 'คงที่' ครั้งสุดท้ายกับแมโคร นอกจากนี้ยังช่วยให้คุณใช้##__VA_ARGS__ในข้อความการแทนที่แมโครซึ่งจะลบเครื่องหมายจุลภาคก่อนหน้าสัญกรณ์หาก แต่โทเค็นก่อนหน้านี้เป็นเครื่องหมายจุลภาค:

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

วิธีการแก้ปัญหานี้ยังคงประโยชน์ของการกำหนดรูปแบบการโต้แย้งในขณะที่การยอมรับข้อโต้แย้งทางเลือกหลังจากรูปแบบ

เทคนิคนี้ยังสนับสนุนโดยClangสำหรับความเข้ากันได้ของ GCC


ทำไมลูป do-while?

จุดประสงค์ของที่do whileนี่คืออะไร

คุณต้องการที่จะสามารถใช้แมโครเพื่อให้ดูเหมือนว่าการเรียกใช้ฟังก์ชันซึ่งหมายความว่ามันจะตามด้วยเซมิโคลอน ดังนั้นคุณต้องแพคเกจแมโครให้เหมาะสม หากคุณใช้ifคำสั่งโดยไม่มีการล้อมdo { ... } while (0)คุณจะมี:

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

ตอนนี้สมมติว่าคุณเขียน:

if (x > y)
    debug_print("x (%d) > y (%d)\n", x, y);
else
    do_something_useful(x, y);

น่าเสียดายที่การเยื้องนั้นไม่ได้สะท้อนการควบคุมการไหลที่แท้จริงเนื่องจากตัวประมวลผลล่วงหน้าสร้างรหัสที่เทียบเท่ากับสิ่งนี้ (เยื้องและวงเล็บปีกกาเพิ่มเพื่อเน้นความหมายที่แท้จริง):

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

ความพยายามครั้งต่อไปที่มาโครอาจเป็น:

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

และในตอนนี้โค้ดส่วนเดียวกันก็สร้าง:

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

และelseตอนนี้เป็นข้อผิดพลาดทางไวยากรณ์ do { ... } while(0)หลีกเลี่ยงห่วงทั้งปัญหาเหล่านี้

มีอีกวิธีหนึ่งในการเขียนแมโครซึ่งอาจใช้งานได้:

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

สิ่งนี้จะทำให้ส่วนของโปรแกรมที่แสดงนั้นถูกต้อง การ(void)โยนป้องกันไม่ให้ใช้ในบริบทที่ต้องการค่า - แต่สามารถใช้เป็นตัวถูกดำเนินการด้านซ้ายของตัวดำเนินการคอมมาที่do { ... } while (0)เวอร์ชันไม่สามารถทำได้ หากคุณคิดว่าคุณควรฝังโค้ดดีบักลงในนิพจน์ดังกล่าวคุณอาจชอบสิ่งนี้ หากคุณต้องการให้การพิมพ์ debug ทำหน้าที่เป็นคำสั่งแบบสมบูรณ์do { ... } while (0)รุ่นนั้นจะดีกว่า โปรดทราบว่าหากเนื้อความของมาโครเกี่ยวข้องกับเซมิโคลอนใด ๆ (พูดโดยประมาณ) คุณจะสามารถใช้do { ... } while(0)สัญลักษณ์เท่านั้น มันได้ผลเสมอ กลไกการแสดงออกทางสีหน้าอาจจะใช้งานได้ยากขึ้น คุณอาจได้รับคำเตือนจากคอมไพเลอร์ด้วยแบบฟอร์มนิพจน์ที่คุณต้องการหลีกเลี่ยง มันจะขึ้นอยู่กับคอมไพเลอร์และธงที่คุณใช้


TPOP ก่อนหน้านี้ที่http://plan9.bell-labs.com/cm/cs/tpopและhttp://cm.bell-labs.com/cm/cs/tpopแต่ตอนนี้ทั้งคู่อยู่ในขณะนี้ (2015-08-10) เสีย


รหัสใน GitHub

ถ้าคุณอยากรู้คุณสามารถดูรหัสนี้ใน GitHub ในของฉันSOQ (คำถามที่กองมากเกิน) พื้นที่เก็บข้อมูลเป็นไฟล์debug.c, debug.hและmddebug.cใน src / libsoq ไดเรกทอรีย่อย


1
ฉันคิดว่า GCC ## - วิธีการจากgcc.gnu.org/onlinedocs/cpp/Variadic-Macros.htmlจะคุ้มค่าที่จะกล่าวถึงภายใต้หัวข้อ "อาร์กิวเมนต์อาร์กิวเมนต์เดี่ยว C99"
Hugo Ideler

2
หลายปีต่อมาและคำตอบนี้ยังคงมีประโยชน์มากที่สุดจาก internets ทั้งหมดเกี่ยวกับวิธีนามแฝง printk! vfprintf ไม่ทำงานในพื้นที่เคอร์เนลเนื่องจาก stdio ไม่พร้อมใช้งาน ขอบคุณ! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
kevinf

6
ในตัวอย่างของคุณด้วยคีย์เวิร์ด__FILE__, __LINE__, __func__, __VA_ARGS__มันจะไม่คอมไพล์ถ้าคุณไม่มีพารามิเตอร์ printf เช่นถ้าคุณเพิ่งโทรdebug_print("Some msg\n"); คุณสามารถแก้ไขได้โดยใช้fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); ## __ VA_ARGS__ ยอมให้ส่งพารามิเตอร์ไปยังฟังก์ชันไม่ได้
mc_electron

1
@LogicTom: แตกต่างระหว่างและ#define debug_print(fmt, ...) #define debug_print(...)ครั้งแรกของเหล่านี้ต้องมีอย่างน้อยหนึ่งอาร์กิวเมนต์สตริงรูปแบบ ( fmt) และศูนย์อื่น ๆ หรือมากกว่าข้อโต้แย้ง; ที่สองต้องมีศูนย์หรือมากกว่าข้อโต้แย้งทั้งหมด หากคุณใช้debug_print()กับตัวแรกคุณจะได้รับข้อผิดพลาดจากตัวประมวลผลล่วงหน้าเกี่ยวกับการใช้แมโครในทางที่ผิดในขณะที่ตัวที่สองไม่ได้ อย่างไรก็ตามคุณยังคงได้รับข้อผิดพลาดในการคอมไพล์เนื่องจากข้อความการแทนที่ไม่ถูกต้อง C. ดังนั้นจริงๆแล้วมันไม่ได้แตกต่างกันมากนัก - ดังนั้นการใช้คำว่า 'การตรวจสอบที่ จำกัด '
Jonathan Leffler

1
ชุดตัวเลือกที่แตกต่างกันคือ @ St.Antario ใช้ระดับการดีบักแบบแอคทีฟเดียวทั่วทั้งแอปพลิเคชันและฉันมักจะใช้ตัวเลือกบรรทัดคำสั่งเพื่อให้สามารถตั้งค่าระดับการดีบักได้เมื่อเรียกใช้โปรแกรม ฉันยังมีตัวแปรที่รับรู้ระบบย่อยที่แตกต่างกันหลายแห่งซึ่งแต่ละตัวจะได้รับชื่อและระดับการดีบักของตัวเองเพื่อให้ฉันสามารถใช้-D input=4,macros=9,rules=2เพื่อตั้งค่าระดับการดีบักของระบบอินพุตเป็น 4 ระบบแมโครเป็น 9 (ภายใต้การตรวจสอบอย่างเข้มงวด) ) และระบบกฎถึง 2 มีรูปแบบที่ไม่มีที่สิ้นสุดในชุดรูปแบบ; ใช้สิ่งที่เหมาะกับคุณ
Jonathan Leffler

28

ฉันใช้สิ่งนี้:

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

กว่าที่ฉันจะใช้ D เป็นคำนำหน้า:

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

คอมไพเลอร์เห็นรหัสการแก้ปัญหาไม่มีปัญหาเครื่องหมายจุลภาคและทำงานได้ทุกที่ นอกจากนี้ยังใช้งานได้เมื่อprintfไม่เพียงพอพูดเมื่อคุณต้องถ่ายโอนข้อมูลอาร์เรย์หรือคำนวณค่าการวินิจฉัยบางอย่างที่ซ้ำซ้อนกับโปรแกรมเอง

แก้ไข: ตกลงมันอาจสร้างปัญหาเมื่อมีelseบางแห่งที่อยู่ใกล้ที่สามารถถูกขัดขวางโดยการฉีดifนี้ นี่คือรุ่นที่มีมากกว่า:

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

3
ในฐานะที่เป็นfor(;0;)มันอาจสร้างปัญหาเมื่อคุณเขียนสิ่งที่ต้องการหรือD continue; D break;
ACcreator

1
เข้าใจฉันแล้ว ดูเหมือนว่าไม่น่าเป็นไปได้มากที่อาจเกิดขึ้นจากอุบัติเหตุ
mbq

11

สำหรับการใช้งานพกพา (ISO C90) คุณสามารถใช้วงเล็บคู่ได้เช่นนี้

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

หรือ (แฮ็คไม่แนะนำ)

#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: เพื่อให้ผู้ประมวลผลล่วงหน้า `คิดว่า 'มีเพียงอาร์กิวเมนต์เดียวในขณะที่ปล่อยให้ _ ขยายในภายหลัง
Marcin Koziuk

10

นี่คือรุ่นที่ฉันใช้:

#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

ฉันจะทำอะไรเช่น

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

ฉันคิดว่ามันสะอาดกว่า


ฉันไม่ชอบความคิดในการใช้มาโครในการทดสอบเป็นแฟล็ก คุณช่วยอธิบายได้ไหมว่าเหตุใดจึงควรตรวจสอบการพิมพ์ debug
LB40

1
@ โจนาธาน: หากรหัสได้รับการดำเนินการในโหมดดีบักเท่านั้นทำไมคุณควรสนใจว่ามันจะรวบรวมในโหมดที่ไม่ใช่การแก้ปัญหา? assert()จาก STDLIB ทำงานในลักษณะเดียวกันและผมได้ตามปกติอีกครั้งเพียงใช้NDEBUGแมโครรหัสแก้จุดบกพร่องของตัวเอง ...
คริสโต

ใช้ DEBUG ในการทดสอบถ้ามีคนทำการยกเลิกการควบคุม undef DEBUG รหัสของคุณจะไม่รวบรวมอีกต่อไป ใช่มั้ย
LB40

4
มันน่าผิดหวังที่จะเปิดใช้งานการดีบักและจากนั้นต้องทำการดีบั๊กรหัสการแก้จุดบกพร่องเพราะมันหมายถึงตัวแปรที่ถูกเปลี่ยนชื่อหรือพิมพ์ใหม่ ฯลฯ หากคอมไพเลอร์ (โพสต์โปรเซสเซอร์ล่วงหน้า) เห็นคำสั่งการพิมพ์เสมอ ไม่ทำให้การวินิจฉัยไม่ถูกต้อง หากคอมไพเลอร์ไม่เห็นข้อความสั่งพิมพ์จะไม่สามารถป้องกันคุณจากความประมาทของคุณเอง (หรือความประมาทของเพื่อนร่วมงานหรือผู้ทำงานร่วมกัน) โปรดดูที่ "การปฏิบัติของการเขียนโปรแกรมโดย Kernighan และหอก - plan9.bell-labs.com/cm/cs/tpop
Jonathan Leffler

1
@Christoph: ดีเรียงลำดับ ... ฉันใช้ NDEBUG เพื่อควบคุมการยืนยันเท่านั้นและแมโครที่แยกต่างหาก (ปกติ DEBUG) เพื่อควบคุมการติดตามการดีบัก ฉันไม่ต้องการให้ debug output ปรากฏขึ้นอย่างไม่มีเงื่อนไขดังนั้นฉันจึงมีกลไกในการควบคุมว่าเอาต์พุตจะปรากฏขึ้น (ระดับ debug และแทนที่จะเรียก fprintf () โดยตรงฉันเรียกฟังก์ชัน debug print ที่พิมพ์แบบมีเงื่อนไขเท่านั้นดังนั้นโครงสร้างเดียวกัน รหัสสามารถพิมพ์หรือไม่พิมพ์ตามตัวเลือกของโปรแกรม) ฉันกำลังสนับสนุนว่าสำหรับการสร้างทั้งหมดคอมไพเลอร์ควรเห็นงบการวินิจฉัย; อย่างไรก็ตามรหัสดังกล่าวจะไม่สร้างรหัสจนกว่าจะเปิดใช้งานการดีบัก
Jonathan Leffler

8

ตามhttp://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.htmlควรจะมี##ก่อนหน้า__VA_ARGS__นี้

มิฉะนั้นแมโครจะไม่รวบรวมตัวอย่างต่อไปนี้:#define dbg_print(format, ...) printf(format, __VA_ARGS__)dbg_print("hello world");


1
ยินดีต้อนรับสู่ Stack Overflow คุณถูกต้องที่ GCC มีส่วนขยายที่ไม่ได้มาตรฐานที่คุณอ้างอิง คำตอบที่ได้รับการยอมรับในปัจจุบันนั้นจริง ๆ แล้วพูดถึงสิ่งนี้รวมถึง URL อ้างอิงที่คุณให้
Jonathan Leffler

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

C เวอร์ชันใดรองรับสัญลักษณ์นั้น และถ้ามันทำงานโทเค็นที่วางอาร์กิวเมนต์ทั้งหมดเช่นนั้นหมายความว่าคุณมีชุดตัวเลือกที่ จำกัด มากสำหรับสตริงรูปแบบใช่ไหม
Jonathan Leffler

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

1
ตกลง - เห็นด้วย: มีการบันทึกไว้เป็นส่วนขยาย GNU เก่า (ส่วน 5.17 ของคู่มือ GCC 4.4.1) แต่คุณควรเอกสารว่ามันจะทำงานกับ GCC - หรือบางทีเราทำอย่างนั้นระหว่างเราในความคิดเห็นเหล่านี้
Jonathan Leffler

1
ความตั้งใจของฉันคือการแสดงรูปแบบการใช้ args อีกแบบและเพื่อแสดงให้เห็นถึงการใช้งานฟังก์ชั่นและLINE
eyalm

2

นี่คือสิ่งที่ฉันใช้:

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

มันมีประโยชน์ที่ดีในการจัดการ printf อย่างถูกต้องแม้ไม่มีข้อโต้แย้งเพิ่มเติม ในกรณีที่ DBG == 0 คอมไพเลอร์ที่โง่เง่าก็ไม่ได้รับการเคี้ยวดังนั้นจึงไม่มีการสร้างรหัส


เป็นการดีกว่าถ้าให้คอมไพเลอร์ตรวจสอบรหัสการดีบักเสมอ
Jonathan Leffler

1

รายการโปรดของฉันด้านล่างคือvar_dumpซึ่งเมื่อเรียกว่า:

var_dump("%d", count);

ผลิตผลเช่น:

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

ขอมอบเครดิตให้แก่ @ "Jonathan Leffler" ทั้งหมดมีความสุข C89:

รหัส

#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

ดังนั้นเมื่อใช้ gcc ฉันชอบ:

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

เพราะมันสามารถแทรกลงในรหัส

สมมติว่าคุณกำลังพยายามแก้ไขข้อบกพร่อง

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

720

จากนั้นคุณสามารถเปลี่ยนเป็น:

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

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

และคุณสามารถวิเคราะห์ว่านิพจน์ใดถูกประเมินเป็นอะไร

มันได้รับการปกป้องจากปัญหาการประเมินซ้ำซ้อน แต่การไม่มี gensyms ทำให้เปิดให้มีการชนกันของชื่อ

อย่างไรก็ตามมันทำรัง:

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

ดังนั้นฉันคิดว่าตราบใดที่คุณหลีกเลี่ยงการใช้ g2rE3 เป็นชื่อตัวแปรคุณจะโอเค

แน่นอนฉันได้พบมัน (และรุ่นพันธมิตรสำหรับสตริงและรุ่นสำหรับระดับการแก้ปัญหา ฯลฯ ) ล้ำค่า


1

ฉัน stewing วิธีการทำเช่นนี้มานานหลายปีและในที่สุดก็มากับวิธีการแก้ปัญหา อย่างไรก็ตามฉันไม่ทราบว่ามีวิธีแก้ไขปัญหาอื่น ๆ อยู่ที่นี่แล้ว อย่างแรกคือข้อแตกต่างกับคำตอบของ Lefflerฉันไม่เห็นข้อโต้แย้งของเขาว่าการแก้ไขข้อบกพร่องควรรวบรวม ฉันไม่ควรมีโค้ดที่ไม่จำเป็นจำนวนมากที่ทำงานในโครงการของฉันเมื่อไม่จำเป็นในกรณีที่ฉันต้องการทดสอบและอาจไม่ได้รับการปรับให้เหมาะสม

การรวบรวมไม่ทุกครั้งอาจฟังดูแย่กว่าที่เป็นจริง คุณปิดท้ายด้วยการแก้ไขข้อบกพร่องที่ไม่ได้รวบรวมในบางครั้ง แต่ก็ไม่ยากที่จะรวบรวมและทดสอบก่อนที่จะจบโครงการ ด้วยระบบนี้หากคุณใช้ debugs สามระดับเพียงแค่ใส่ debug ระดับข้อความที่สามแก้ไขข้อผิดพลาดในการคอมไพล์ของคุณและตรวจสอบหาข้อผิดพลาดอื่น ๆ ก่อนที่คุณจะจบรหัส yer (ตั้งแต่แน่นอนการรวบรวมคำสั่งการดีบักไม่รับประกันว่าจะยังคงทำงานตามที่ตั้งใจ)

โซลูชันของฉันให้ระดับรายละเอียดการดีบักเช่นกัน และถ้าคุณตั้งไว้ที่ระดับสูงสุดพวกเขาทั้งหมดรวบรวม หากคุณใช้ระดับรายละเอียดการดีบักสูงเมื่อเร็ว ๆ นี้พวกเขาทั้งหมดสามารถรวบรวมได้ในเวลานั้น การปรับปรุงครั้งสุดท้ายควรเป็นเรื่องง่าย ฉันไม่เคยต้องการมากกว่าสามระดับ แต่โจนาธานบอกว่าเขาใช้เก้าระดับ วิธีนี้ (เช่นของ Leffler) สามารถขยายออกไปได้หลายระดับ การใช้วิธีการของฉันอาจจะง่ายกว่า ต้องการเพียงสองคำสั่งเมื่อใช้ในรหัสของคุณ อย่างไรก็ตามฉันก็เขียนโค้ดมาโครปิดเช่นกัน - ถึงแม้ว่ามันจะไม่ทำอะไรเลย อาจถ้าฉันถูกส่งไปยังไฟล์

เทียบกับค่าใช้จ่ายขั้นตอนพิเศษของการทดสอบพวกเขาเพื่อดูว่าพวกเขาจะรวบรวมก่อนส่งมอบคือว่า

  1. คุณต้องเชื่อใจพวกเขาในการเพิ่มประสิทธิภาพซึ่งเป็นที่ยอมรับว่าควรเกิดขึ้นหากคุณมีระดับการเพิ่มประสิทธิภาพที่เพียงพอ
  2. นอกจากนี้พวกเขาอาจจะไม่ถ้าคุณทำการคอมไพล์ด้วยการปิดการปรับให้เหมาะสมสำหรับวัตถุประสงค์ในการทดสอบ (ซึ่งเป็นที่ยอมรับได้ยาก); และพวกเขาแทบจะไม่ได้เลยในระหว่างการดีบัก - ดังนั้นการดำเนินการคำสั่ง "if (DEBUG)" ที่รันไทม์; ดังนั้นการดำเนินการที่ช้าลง (ซึ่งเป็นหลักการคัดค้านของฉัน) และที่สำคัญน้อยกว่าคือการเพิ่มขนาดไฟล์เรียกทำงานหรือ dll ของคุณ และด้วยเหตุนี้การดำเนินการและเวลารวบรวม อย่างไรก็ตามโจนาธานบอกผมว่าวิธีการของเขาสามารถทำเพื่อไม่ให้คอมไพล์ข้อความได้เลย

สาขามีราคาค่อนข้างแพงในโปรเซสเซอร์ดึงข้อมูลที่ทันสมัย อาจไม่ใช่เรื่องใหญ่หากแอปของคุณไม่ใช่ช่วงเวลาที่สำคัญ แต่ถ้าประสิทธิภาพเป็นปัญหาแล้วก็ใช่ว่าเป็นข้อตกลงที่ใหญ่พอที่ฉันต้องการเลือกใช้รหัสการดีบักที่ค่อนข้างเร็วขึ้น (และอาจปล่อยได้เร็วกว่าในกรณีที่ไม่ค่อยเกิดขึ้นตามที่ระบุไว้)

ดังนั้นสิ่งที่ฉันต้องการคือมาโครการพิมพ์ดีบั๊กที่ไม่ได้รวบรวมถ้ามันไม่ได้ถูกพิมพ์ แต่ทำถ้ามันเป็น ฉันต้องการระดับการดีบักเช่นถ้าฉันต้องการชิ้นส่วนที่มีความสำคัญต่อประสิทธิภาพของรหัสไม่ให้พิมพ์ในบางครั้ง แต่เมื่อต้องการพิมพ์ที่อื่นฉันสามารถตั้งค่าระดับการดีบักและพิมพ์ debug พิเศษได้ พบวิธีการใช้ระดับการแก้ปัญหาที่กำหนดว่าพิมพ์ได้รวบรวมหรือไม่ ฉันได้มาโดยวิธีนี้:

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

การใช้มาโคร

หากต้องการใช้เพียงทำ:

DEBUGLOG_INIT("afile.log");

หากต้องการเขียนไปยังล็อกไฟล์ให้ทำดังนี้

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

เพื่อปิดมันคุณทำ:

DEBUGLOG_CLOSE();

แม้ว่าในปัจจุบันนี้ไม่จำเป็นแม้แต่พูดเทคนิคเพราะมันไม่ทำอะไรเลย ฉันยังคงใช้ CLOSE อยู่ในตอนนี้อย่างไรก็ตามในกรณีที่ฉันเปลี่ยนใจเกี่ยวกับการทำงานและต้องการให้ไฟล์เปิดอยู่ระหว่างการบันทึกคำสั่ง

จากนั้นเมื่อคุณต้องการเปิดการพิมพ์ debug เพียงแค่แก้ไข #define แรกในไฟล์ส่วนหัวเพื่อพูดเช่น

#define DEBUG 1

หากต้องการให้คำสั่งการบันทึกรวบรวมสิ่งใดให้ทำ

#define DEBUG 0

หากคุณต้องการข้อมูลจากโค้ดที่ถูกเรียกใช้บ่อย (เช่นรายละเอียดในระดับสูง) คุณอาจต้องการเขียน:

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

ถ้าคุณกำหนด DEBUG ให้เป็น 3 การคอมไพล์ระดับการบันทึก 1, 2 และ 3 หากคุณตั้งค่าเป็น 2 คุณจะได้รับระดับการบันทึก 1 และ 2 หากคุณตั้งค่าเป็น 1 คุณจะได้รับข้อความระดับการบันทึก 1 เท่านั้น

สำหรับห่วง do-while เนื่องจากประเมินว่าเป็นฟังก์ชันเดี่ยวหรือไม่มีอะไรเลยแทนที่จะเป็นคำสั่ง if จึงไม่จำเป็นต้องใช้ลูป ตกลงเลิกฉันด้วยการใช้ C แทน C ++ IO (และ QString :: arg () ของ Qt เป็นวิธีที่ปลอดภัยกว่าในการจัดรูปแบบตัวแปรเมื่ออยู่ใน Qt เช่นกัน - มันค่อนข้างเนียน แต่ใช้รหัสเพิ่มเติมและเอกสารการจัดรูปแบบไม่ได้จัดระเบียบ อย่างที่มันอาจจะ - แต่ถึงกระนั้นฉันก็พบกรณีที่มันน่าจะดีกว่า) แต่คุณสามารถใส่โค้ดอะไรก็ได้ในไฟล์. cpp ที่คุณต้องการ มันอาจจะเป็นคลาส แต่จากนั้นคุณจะต้องสร้างอินสแตนซ์และติดตามมันหรือทำใหม่ () และเก็บไว้ ด้วยวิธีนี้คุณเพียงวาง #include เริ่มต้นและปิดคำสั่งแบบเลือกลงในแหล่งที่มาของคุณและคุณพร้อมที่จะเริ่มใช้งาน มันจะทำให้ชั้นดี แต่ถ้าคุณมีความโน้มเอียงดังนั้น

ก่อนหน้านี้ฉันเคยเห็นวิธีแก้ปัญหามากมาย แต่ไม่มีใครเหมาะสมกับเกณฑ์ของฉันเช่นเดียวกับวิธีนี้

  1. มันสามารถขยายออกไปได้หลายระดับตามที่คุณต้องการ
  2. มันรวบรวมเพื่ออะไรถ้าไม่ได้พิมพ์
  3. มันรวมศูนย์ IO ไว้ในที่เดียวที่ง่ายต่อการแก้ไข
  4. ยืดหยุ่นได้โดยใช้การจัดรูปแบบพิมพ์
  5. อีกครั้งจะไม่ทำให้การดีบั๊กช้าลงในขณะที่การดีบั๊กการคอมไพล์ทุกครั้งจะถูกดำเนินการในโหมดดีบั๊กเสมอ หากคุณกำลังทำวิทยาศาสตร์คอมพิวเตอร์และไม่ง่ายต่อการเขียนการประมวลผลข้อมูลคุณอาจพบว่าตัวเองกำลังใช้ตัวจำลองที่ใช้ CPU เพื่อดูว่าตัวแก้จุดบกพร่องหยุดทำงานโดยมีดัชนีอยู่นอกขอบเขตของเวกเตอร์ เหล่านี้ทำงานช้าลงเป็นพิเศษในโหมดแก้ไขข้อบกพร่องแล้ว การบังคับให้พิมพ์ debug ได้หลายครั้งจะทำให้การทำงานดังกล่าวช้าลง สำหรับฉันแล้วการวิ่งเช่นนี้ไม่ใช่เรื่องแปลก

ไม่สำคัญมาก แต่เพิ่มเติม:

  1. มันไม่ต้องแฮ็คเพื่อพิมพ์โดยไม่มีข้อโต้แย้ง (เช่นDEBUGLOG_LOG(3, "got here!");); ทำให้คุณสามารถใช้งานได้เช่นการจัดรูปแบบ .arg () ที่ปลอดภัยยิ่งขึ้นของ Qt มันใช้งานได้กับ MSVC และอาจเป็น gcc มันใช้##ใน#defines ซึ่งไม่ได้มาตรฐานตามที่ Leffler ชี้ให้เห็น แต่ได้รับการสนับสนุนอย่างกว้างขวาง (คุณสามารถบันทึกใหม่ไม่ให้ใช้##หากจำเป็น แต่คุณจะต้องใช้แฮ็คอย่างที่เขาให้ไว้)

คำเตือน: หากคุณลืมระบุอาร์กิวเมนต์ระดับการบันทึกข้อมูล MSVC จะไม่สามารถระบุตัวระบุได้โดยไม่ช่วยเหลือ

คุณอาจต้องการใช้ชื่อสัญลักษณ์ตัวประมวลผลล่วงหน้านอกเหนือจาก DEBUG เนื่องจากบางแหล่งยังกำหนดสัญลักษณ์นั้น (เช่น progs โดยใช้./configureคำสั่งเพื่อเตรียมการสร้าง) มันดูเหมือนเป็นธรรมชาติสำหรับฉันเมื่อฉันพัฒนามัน ฉันพัฒนามันในแอพพลิเคชั่นที่มีการใช้งาน DLL อย่างอื่นและมันเป็นคอนแวนต์มากกว่าในการส่งไฟล์บันทึกการพิมพ์ไปยังไฟล์ แต่การเปลี่ยนเป็น vprintf () ก็ใช้ได้ดีเช่นกัน

ฉันหวังว่าสิ่งนี้จะช่วยให้คุณหลายคนเศร้าใจเกี่ยวกับการหาวิธีที่ดีที่สุดในการบันทึกการดีบัก หรือแสดงให้คุณเห็นหนึ่งที่คุณอาจชอบ ฉันพยายามอย่างเต็มที่ที่จะหาสิ่งนี้มานานหลายทศวรรษ ทำงานใน MSVC 2012 และ 2015 และอาจเป็น gcc; เช่นเดียวกับอาจทำงานกับคนอื่น ๆ อีกมากมาย แต่ฉันไม่ได้ทดสอบพวกเขา

ฉันตั้งใจจะสร้างเวอร์ชั่นสตรีมมิ่งของวันนี้เช่นกัน

หมายเหตุ: ขอบคุณไปที่ Leffler ผู้ซึ่งช่วยฉันจัดรูปแบบข้อความได้ดีขึ้นสำหรับ StackOverflow


2
คุณพูดว่า "การดำเนินการหลายสิบหรือหลายร้อยif (DEBUG)งบที่รันไทม์ซึ่งไม่ได้รับการปรับให้เหมาะสมออก" - ซึ่งเป็นเอียงที่กังหันลม จุดทั้งหมดของระบบที่ฉันอธิบายคือโค้ดตรวจสอบโดยคอมไพเลอร์ (สำคัญและอัตโนมัติ - ไม่จำเป็นต้องมีบิลด์พิเศษ) แต่รหัสดีบั๊กไม่ได้ถูกสร้างขึ้นเลยเพราะมันถูกปรับให้เหมาะสม ขนาดรหัสหรือประสิทธิภาพเนื่องจากรหัสไม่ปรากฏที่รันไทม์)
Jonathan Leffler

Jonathan Leffler: ขอบคุณสำหรับการชี้ถ้อยคำผิด ๆ ของฉัน ฉันปล่อยให้ความคิดของฉันแข่งเร็วกว่านิ้วของฉันดีใจที่ทำสิ่งนี้เสร็จ ฉันได้แก้ไขคำคัดค้านด้วย "... 1) คุณต้องเชื่อใจพวกเขาเพื่อให้ได้ประสิทธิภาพสูงสุดซึ่งเป็นที่ยอมรับว่าควรเกิดขึ้นหากคุณมีระดับการเพิ่มประสิทธิภาพที่เพียงพอ 2) นอกจากนี้พวกเขาจะไม่ทำเช่นนั้นหากคุณทำการคอมไพล์ ปิดเพื่อวัตถุประสงค์ในการทดสอบและพวกเขาอาจจะไม่ได้เลยในระหว่างการแก้ปัญหา - ดังนั้นการดำเนินการงบ 'if (DEBUG)' หลายสิบหรือร้อยของรันไทม์ - จึงเพิ่มขนาดปฏิบัติการหรือ dll ของคุณและเวลาดำเนินการ "
CodeLurker

เพื่อให้คุณทำสิ่งที่สำคัญอื่น ๆ ที่ฉันกำลังทำอยู่คุณจะต้องมีระดับการดีบัก ในขณะที่ฉันมักไม่ต้องการเปิดใช้งานจำนวนมาก แต่แอปพลิเคชั่นบางตัวได้รับประโยชน์จากความสามารถในการรับรายละเอียดระดับสูงเกี่ยวกับการวนซ้ำตามเวลาที่สำคัญด้วย "#define DEBUG 3" แบบง่าย ๆ จากนั้นกลับไป ข้อมูล verbose น้อยลงด้วย "#define DEBUG 1" ฉันไม่เคยต้องการมากกว่าสามระดับและอย่างน้อยประมาณ 1/3 ของ debugs ของฉันรวบรวมได้แล้ว ถ้าฉันใช้ระดับ 3 เมื่อเร็ว ๆ นี้พวกเขาอาจทำทั้งหมด
CodeLurker

YMMV ระบบที่ทันสมัยที่ฉันแสดงให้เห็นสนับสนุนการตั้งค่าระดับการดีบักแบบไดนามิก (รันไทม์) ดังนั้นคุณสามารถตัดสินใจได้ว่าจะทำการดีบักจำนวนเท่าใดในขณะทำการเขียนโปรแกรม ฉันมักจะใช้ระดับ 1-9 แม้ว่าจะไม่มีขีด จำกัด บน (หรือขีด จำกัด ต่ำกว่าระดับเริ่มต้นคือ 0 ซึ่งมักจะปิด แต่สามารถขออย่างชัดเจนในระหว่างการพัฒนาที่ใช้งานอยู่ถ้าเหมาะสม - มันไม่เหมาะสมสำหรับการทำงานในระยะยาว) ฉันเลือกระดับเริ่มต้นที่ 3 สิ่งที่สามารถปรับได้ นี่ทำให้ฉันควบคุมได้มาก หากคุณไม่ต้องการทดสอบรหัสการดีบักเมื่อไม่ได้ใช้งานให้เปลี่ยนทางเลือกอื่น((void)0)- ง่าย
Jonathan Leffler

1
อ่า มันจะช่วยให้ได้อ่านทุกสิ่ง มันเป็นโพสต์ที่ค่อนข้างยาว ฉันคิดว่านั่นคือจุดสำคัญที่ป่านนี้ ปรากฎเป็นของคุณเช่นเดียวกับฉันสามารถใช้ในการรวบรวมหรือไม่รวบรวมการแก้ไขข้อบกพร่องทั้งหมดและสามารถรองรับระดับ แม้ว่าจะเป็นที่ยอมรับคุณสามารถรวบรวมระดับที่คุณไม่ได้ใช้ - มีค่าใช้จ่ายระหว่างการดีบัก
CodeLurker

0

ฉันเชื่อว่ารูปแบบของชุดรูปแบบนี้ให้หมวดหมู่การดีบักโดยไม่จำเป็นต้องมีชื่อแมโครแยกต่างหากต่อหมวดหมู่

ฉันใช้รูปแบบนี้ในโครงการ Arduino ซึ่งพื้นที่โปรแกรม จำกัด เพียง 32K และหน่วยความจำแบบไดนามิก จำกัด อยู่ที่ 2K การเพิ่มคำสั่งดีบักและสตริงการดีบักการติดตามใช้พื้นที่อย่างรวดเร็ว ดังนั้นจึงเป็นสิ่งจำเป็นที่จะสามารถ จำกัด การติดตามการดีบักที่รวมอยู่ในเวลารวบรวมให้น้อยที่สุดเท่าที่จำเป็นในแต่ละครั้งที่มีการสร้างรหัส

debug.h

#ifndef DEBUG_H
#define DEBUG_H

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

#endif

เรียกไฟล์. cpp

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

...
PRINT(4, "Time out error,\t");
...
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.