กำลังพยายามสร้างแมโครซึ่งสามารถใช้สำหรับข้อความดีบักการพิมพ์เมื่อกำหนด DEBUG เช่นรหัสเทียมต่อไปนี้:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
การทำมาโครนี้ทำได้อย่างไร
กำลังพยายามสร้างแมโครซึ่งสามารถใช้สำหรับข้อความดีบักการพิมพ์เมื่อกำหนด DEBUG เช่นรหัสเทียมต่อไปนี้:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
การทำมาโครนี้ทำได้อย่างไร
คำตอบ:
#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 และไม่มีส่วนขยายคอมไพเลอร์ที่มีประโยชน์แสดงว่าไม่มีวิธีที่ชัดเจนในการจัดการกับมัน เทคนิคที่ฉันเคยใช้คือ:
#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 ที่พิมพ์ตามเงื่อนไขเท่านั้น ดังนั้นการสร้างรหัสเดียวกันสามารถพิมพ์หรือไม่พิมพ์ตามตัวเลือกของโปรแกรม) ฉันยังมีรหัส 'หลายระบบย่อย' สำหรับโปรแกรมที่ใหญ่กว่าเพื่อให้ฉันสามารถมีส่วนต่าง ๆ ของโปรแกรมที่สร้างจำนวนการติดตามที่แตกต่างกัน - ภายใต้การควบคุมแบบรันไทม์
ฉันกำลังสนับสนุนว่าสำหรับการสร้างทั้งหมดคอมไพเลอร์ควรเห็นงบการวินิจฉัย; อย่างไรก็ตามคอมไพเลอร์จะไม่สร้างรหัสใด ๆ สำหรับคำสั่งการติดตามการดีบักจนกว่าจะเปิดใช้งานการดีบัก โดยทั่วไปหมายความว่าโค้ดทั้งหมดของคุณจะถูกตรวจสอบโดยคอมไพเลอร์ทุกครั้งที่คุณคอมไพล์ไม่ว่าจะเป็นการรีลีสหรือการดีบั๊ก นี่เป็นสิ่งที่ดี!
/*
@(#)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 */
/*
@(#)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 */
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()
จะไม่สามารถรวบรวมได้) . การสูญเสียการตรวจสอบเป็นปัญหาหรือไม่
คอมไพเลอร์บางตัวอาจเสนอส่วนขยายสำหรับวิธีอื่นในการจัดการรายการอาร์กิวเมนต์ที่มีความยาวผันแปรได้ในแมโคร โดยเฉพาะอย่างยิ่งตามที่ระบุไว้เป็นครั้งแรกในความคิดเห็นโดยHugo Ideler , GCC ช่วยให้คุณสามารถละเว้นเครื่องหมายจุลภาคที่ปกติจะปรากฏขึ้นหลังจากอาร์กิวเมนต์ 'คงที่' ครั้งสุดท้ายกับแมโคร นอกจากนี้ยังช่วยให้คุณใช้##__VA_ARGS__
ในข้อความการแทนที่แมโครซึ่งจะลบเครื่องหมายจุลภาคก่อนหน้าสัญกรณ์หาก แต่โทเค็นก่อนหน้านี้เป็นเครื่องหมายจุลภาค:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
วิธีการแก้ปัญหานี้ยังคงประโยชน์ของการกำหนดรูปแบบการโต้แย้งในขณะที่การยอมรับข้อโต้แย้งทางเลือกหลังจากรูปแบบ
เทคนิคนี้ยังสนับสนุนโดยClangสำหรับความเข้ากันได้ของ GCC
จุดประสงค์ของที่
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 ในของฉันSOQ (คำถามที่กองมากเกิน) พื้นที่เก็บข้อมูลเป็นไฟล์debug.c
, debug.h
และmddebug.c
ใน
src / libsoq
ไดเรกทอรีย่อย
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
มันจะไม่คอมไพล์ถ้าคุณไม่มีพารามิเตอร์ printf เช่นถ้าคุณเพิ่งโทรdebug_print("Some msg\n");
คุณสามารถแก้ไขได้โดยใช้fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
## __ VA_ARGS__ ยอมให้ส่งพารามิเตอร์ไปยังฟังก์ชันไม่ได้
#define debug_print(fmt, ...)
#define debug_print(...)
ครั้งแรกของเหล่านี้ต้องมีอย่างน้อยหนึ่งอาร์กิวเมนต์สตริงรูปแบบ ( fmt
) และศูนย์อื่น ๆ หรือมากกว่าข้อโต้แย้ง; ที่สองต้องมีศูนย์หรือมากกว่าข้อโต้แย้งทั้งหมด หากคุณใช้debug_print()
กับตัวแรกคุณจะได้รับข้อผิดพลาดจากตัวประมวลผลล่วงหน้าเกี่ยวกับการใช้แมโครในทางที่ผิดในขณะที่ตัวที่สองไม่ได้ อย่างไรก็ตามคุณยังคงได้รับข้อผิดพลาดในการคอมไพล์เนื่องจากข้อความการแทนที่ไม่ถูกต้อง C. ดังนั้นจริงๆแล้วมันไม่ได้แตกต่างกันมากนัก - ดังนั้นการใช้คำว่า 'การตรวจสอบที่ จำกัด '
-D input=4,macros=9,rules=2
เพื่อตั้งค่าระดับการดีบักของระบบอินพุตเป็น 4 ระบบแมโครเป็น 9 (ภายใต้การตรวจสอบอย่างเข้มงวด) ) และระบบกฎถึง 2 มีรูปแบบที่ไม่มีที่สิ้นสุดในชุดรูปแบบ; ใช้สิ่งที่เหมาะกับคุณ
ฉันใช้สิ่งนี้:
#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
for(;0;)
มันอาจสร้างปัญหาเมื่อคุณเขียนสิ่งที่ต้องการหรือD continue;
D break;
สำหรับการใช้งานพกพา (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;
}
นี่คือรุ่นที่ฉันใช้:
#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
ฉันจะทำอะไรเช่น
#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif
ฉันคิดว่ามันสะอาดกว่า
assert()
จาก STDLIB ทำงานในลักษณะเดียวกันและผมได้ตามปกติอีกครั้งเพียงใช้NDEBUG
แมโครรหัสแก้จุดบกพร่องของตัวเอง ...
ตามhttp://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.htmlควรจะมี##
ก่อนหน้า__VA_ARGS__
นี้
มิฉะนั้นแมโครจะไม่รวบรวมตัวอย่างต่อไปนี้:#define dbg_print(format, ...) printf(format, __VA_ARGS__)
dbg_print("hello world");
#define debug_print(FMT, ARGS...) do { \
if (DEBUG) \
fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
} while (0)
นี่คือสิ่งที่ฉันใช้:
#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/
#endif
มันมีประโยชน์ที่ดีในการจัดการ printf อย่างถูกต้องแม้ไม่มีข้อโต้แย้งเพิ่มเติม ในกรณีที่ DBG == 0 คอมไพเลอร์ที่โง่เง่าก็ไม่ได้รับการเคี้ยวดังนั้นจึงไม่มีการสร้างรหัส
รายการโปรดของฉันด้านล่างคือ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)
ดังนั้นเมื่อใช้ 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 เป็นชื่อตัวแปรคุณจะโอเค
แน่นอนฉันได้พบมัน (และรุ่นพันธมิตรสำหรับสตริงและรุ่นสำหรับระดับการแก้ปัญหา ฯลฯ ) ล้ำค่า
ฉัน stewing วิธีการทำเช่นนี้มานานหลายปีและในที่สุดก็มากับวิธีการแก้ปัญหา อย่างไรก็ตามฉันไม่ทราบว่ามีวิธีแก้ไขปัญหาอื่น ๆ อยู่ที่นี่แล้ว อย่างแรกคือข้อแตกต่างกับคำตอบของ Lefflerฉันไม่เห็นข้อโต้แย้งของเขาว่าการแก้ไขข้อบกพร่องควรรวบรวม ฉันไม่ควรมีโค้ดที่ไม่จำเป็นจำนวนมากที่ทำงานในโครงการของฉันเมื่อไม่จำเป็นในกรณีที่ฉันต้องการทดสอบและอาจไม่ได้รับการปรับให้เหมาะสม
การรวบรวมไม่ทุกครั้งอาจฟังดูแย่กว่าที่เป็นจริง คุณปิดท้ายด้วยการแก้ไขข้อบกพร่องที่ไม่ได้รวบรวมในบางครั้ง แต่ก็ไม่ยากที่จะรวบรวมและทดสอบก่อนที่จะจบโครงการ ด้วยระบบนี้หากคุณใช้ debugs สามระดับเพียงแค่ใส่ debug ระดับข้อความที่สามแก้ไขข้อผิดพลาดในการคอมไพล์ของคุณและตรวจสอบหาข้อผิดพลาดอื่น ๆ ก่อนที่คุณจะจบรหัส yer (ตั้งแต่แน่นอนการรวบรวมคำสั่งการดีบักไม่รับประกันว่าจะยังคงทำงานตามที่ตั้งใจ)
โซลูชันของฉันให้ระดับรายละเอียดการดีบักเช่นกัน และถ้าคุณตั้งไว้ที่ระดับสูงสุดพวกเขาทั้งหมดรวบรวม หากคุณใช้ระดับรายละเอียดการดีบักสูงเมื่อเร็ว ๆ นี้พวกเขาทั้งหมดสามารถรวบรวมได้ในเวลานั้น การปรับปรุงครั้งสุดท้ายควรเป็นเรื่องง่าย ฉันไม่เคยต้องการมากกว่าสามระดับ แต่โจนาธานบอกว่าเขาใช้เก้าระดับ วิธีนี้ (เช่นของ Leffler) สามารถขยายออกไปได้หลายระดับ การใช้วิธีการของฉันอาจจะง่ายกว่า ต้องการเพียงสองคำสั่งเมื่อใช้ในรหัสของคุณ อย่างไรก็ตามฉันก็เขียนโค้ดมาโครปิดเช่นกัน - ถึงแม้ว่ามันจะไม่ทำอะไรเลย อาจถ้าฉันถูกส่งไปยังไฟล์
เทียบกับค่าใช้จ่ายขั้นตอนพิเศษของการทดสอบพวกเขาเพื่อดูว่าพวกเขาจะรวบรวมก่อนส่งมอบคือว่า
สาขามีราคาค่อนข้างแพงในโปรเซสเซอร์ดึงข้อมูลที่ทันสมัย อาจไม่ใช่เรื่องใหญ่หากแอปของคุณไม่ใช่ช่วงเวลาที่สำคัญ แต่ถ้าประสิทธิภาพเป็นปัญหาแล้วก็ใช่ว่าเป็นข้อตกลงที่ใหญ่พอที่ฉันต้องการเลือกใช้รหัสการดีบักที่ค่อนข้างเร็วขึ้น (และอาจปล่อยได้เร็วกว่าในกรณีที่ไม่ค่อยเกิดขึ้นตามที่ระบุไว้)
ดังนั้นสิ่งที่ฉันต้องการคือมาโครการพิมพ์ดีบั๊กที่ไม่ได้รวบรวมถ้ามันไม่ได้ถูกพิมพ์ แต่ทำถ้ามันเป็น ฉันต้องการระดับการดีบักเช่นถ้าฉันต้องการชิ้นส่วนที่มีความสำคัญต่อประสิทธิภาพของรหัสไม่ให้พิมพ์ในบางครั้ง แต่เมื่อต้องการพิมพ์ที่อื่นฉันสามารถตั้งค่าระดับการดีบักและพิมพ์ debug พิเศษได้ พบวิธีการใช้ระดับการแก้ปัญหาที่กำหนดว่าพิมพ์ได้รวบรวมหรือไม่ ฉันได้มาโดยวิธีนี้:
// 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, ...);
// 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 เริ่มต้นและปิดคำสั่งแบบเลือกลงในแหล่งที่มาของคุณและคุณพร้อมที่จะเริ่มใช้งาน มันจะทำให้ชั้นดี แต่ถ้าคุณมีความโน้มเอียงดังนั้น
ก่อนหน้านี้ฉันเคยเห็นวิธีแก้ปัญหามากมาย แต่ไม่มีใครเหมาะสมกับเกณฑ์ของฉันเช่นเดียวกับวิธีนี้
ไม่สำคัญมาก แต่เพิ่มเติม:
DEBUGLOG_LOG(3, "got here!");
); ทำให้คุณสามารถใช้งานได้เช่นการจัดรูปแบบ .arg () ที่ปลอดภัยยิ่งขึ้นของ Qt มันใช้งานได้กับ MSVC และอาจเป็น gcc มันใช้##
ใน#define
s ซึ่งไม่ได้มาตรฐานตามที่ Leffler ชี้ให้เห็น แต่ได้รับการสนับสนุนอย่างกว้างขวาง (คุณสามารถบันทึกใหม่ไม่ให้ใช้##
หากจำเป็น แต่คุณจะต้องใช้แฮ็คอย่างที่เขาให้ไว้)คำเตือน: หากคุณลืมระบุอาร์กิวเมนต์ระดับการบันทึกข้อมูล MSVC จะไม่สามารถระบุตัวระบุได้โดยไม่ช่วยเหลือ
คุณอาจต้องการใช้ชื่อสัญลักษณ์ตัวประมวลผลล่วงหน้านอกเหนือจาก DEBUG เนื่องจากบางแหล่งยังกำหนดสัญลักษณ์นั้น (เช่น progs โดยใช้./configure
คำสั่งเพื่อเตรียมการสร้าง) มันดูเหมือนเป็นธรรมชาติสำหรับฉันเมื่อฉันพัฒนามัน ฉันพัฒนามันในแอพพลิเคชั่นที่มีการใช้งาน DLL อย่างอื่นและมันเป็นคอนแวนต์มากกว่าในการส่งไฟล์บันทึกการพิมพ์ไปยังไฟล์ แต่การเปลี่ยนเป็น vprintf () ก็ใช้ได้ดีเช่นกัน
ฉันหวังว่าสิ่งนี้จะช่วยให้คุณหลายคนเศร้าใจเกี่ยวกับการหาวิธีที่ดีที่สุดในการบันทึกการดีบัก หรือแสดงให้คุณเห็นหนึ่งที่คุณอาจชอบ ฉันพยายามอย่างเต็มที่ที่จะหาสิ่งนี้มานานหลายทศวรรษ ทำงานใน MSVC 2012 และ 2015 และอาจเป็น gcc; เช่นเดียวกับอาจทำงานกับคนอื่น ๆ อีกมากมาย แต่ฉันไม่ได้ทดสอบพวกเขา
ฉันตั้งใจจะสร้างเวอร์ชั่นสตรีมมิ่งของวันนี้เช่นกัน
หมายเหตุ: ขอบคุณไปที่ Leffler ผู้ซึ่งช่วยฉันจัดรูปแบบข้อความได้ดีขึ้นสำหรับ StackOverflow
if (DEBUG)
งบที่รันไทม์ซึ่งไม่ได้รับการปรับให้เหมาะสมออก" - ซึ่งเป็นเอียงที่กังหันลม จุดทั้งหมดของระบบที่ฉันอธิบายคือโค้ดตรวจสอบโดยคอมไพเลอร์ (สำคัญและอัตโนมัติ - ไม่จำเป็นต้องมีบิลด์พิเศษ) แต่รหัสดีบั๊กไม่ได้ถูกสร้างขึ้นเลยเพราะมันถูกปรับให้เหมาะสม ขนาดรหัสหรือประสิทธิภาพเนื่องจากรหัสไม่ปรากฏที่รันไทม์)
((void)0)
- ง่าย
ฉันเชื่อว่ารูปแบบของชุดรูปแบบนี้ให้หมวดหมู่การดีบักโดยไม่จำเป็นต้องมีชื่อแมโครแยกต่างหากต่อหมวดหมู่
ฉันใช้รูปแบบนี้ในโครงการ Arduino ซึ่งพื้นที่โปรแกรม จำกัด เพียง 32K และหน่วยความจำแบบไดนามิก จำกัด อยู่ที่ 2K การเพิ่มคำสั่งดีบักและสตริงการดีบักการติดตามใช้พื้นที่อย่างรวดเร็ว ดังนั้นจึงเป็นสิ่งจำเป็นที่จะสามารถ จำกัด การติดตามการดีบักที่รวมอยู่ในเวลารวบรวมให้น้อยที่สุดเท่าที่จำเป็นในแต่ละครั้งที่มีการสร้างรหัส
#ifndef DEBUG_H
#define DEBUG_H
#define PRINT(DEBUG_CATEGORY, VALUE) do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);
#endif
#define DEBUG_MASK 0x06
#include "Debug.h"
...
PRINT(4, "Time out error,\t");
...