Cố gắng tạo một macro có thể được sử dụng để in thông báo gỡ lỗi khi DEBUG được xác định, như mã giả sau đây:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
Làm thế nào điều này được thực hiện với một macro?
Cố gắng tạo một macro có thể được sử dụng để in thông báo gỡ lỗi khi DEBUG được xác định, như mã giả sau đây:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
Làm thế nào điều này được thực hiện với một macro?
Câu trả lời:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
Nó giả sử bạn đang sử dụng C99 (ký hiệu danh sách đối số biến không được hỗ trợ trong các phiên bản trước). Thành do { ... } while (0)
ngữ đảm bảo rằng mã hoạt động như một câu lệnh (gọi hàm). Việc sử dụng mã vô điều kiện đảm bảo rằng trình biên dịch luôn kiểm tra xem mã gỡ lỗi của bạn có hợp lệ không - nhưng trình tối ưu hóa sẽ xóa mã khi DEBUG bằng 0.
Nếu bạn muốn làm việc với #ifdef DEBUG, thì hãy thay đổi điều kiện kiểm tra:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
Và sau đó sử dụng DEBUG_TEST nơi tôi đã sử dụng DEBUG.
Nếu bạn nhấn mạnh vào một chuỗi ký tự cho chuỗi định dạng (dù sao cũng có thể là một ý tưởng tốt), bạn cũng có thể giới thiệu những thứ như __FILE__
, __LINE__
và __func__
vào đầu ra, có thể cải thiện chẩn đoán:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
Điều này phụ thuộc vào nối chuỗi để tạo ra một chuỗi định dạng lớn hơn so với lập trình viên viết.
Nếu bạn bị mắc kẹt với C89 và không có phần mở rộng trình biên dịch hữu ích, thì đó không phải là cách đặc biệt rõ ràng để xử lý nó. Kỹ thuật tôi từng sử dụng là:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
Và sau đó, trong mã, viết:
TRACE(("message %d\n", var));
Dấu ngoặc kép rất quan trọng - và là lý do tại sao bạn có ký hiệu vui trong việc mở rộng macro. Như trước đây, trình biên dịch luôn kiểm tra mã cho tính hợp lệ cú pháp (tốt) nhưng trình tối ưu hóa chỉ gọi hàm in nếu macro DEBUG ước lượng khác không.
Điều này không yêu cầu một hàm hỗ trợ - trong ví dụ dbg_printf () - để xử lý những thứ như 'stderr'. Nó đòi hỏi bạn phải biết cách viết các hàm varargs, nhưng điều đó không khó:
#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);
}
Tất nhiên, bạn cũng có thể sử dụng kỹ thuật này trong C99, nhưng __VA_ARGS__
kỹ thuật này gọn gàng hơn vì nó sử dụng ký hiệu chức năng thông thường, không phải là hack ngoặc kép.
[ Bình luận lại được thực hiện cho một câu trả lời khác. ]
Một ý tưởng trung tâm đằng sau cả hai triển khai C99 và C89 ở trên là trình biên dịch thích hợp luôn nhìn thấy các câu lệnh giống như printf. Điều này rất quan trọng đối với mã dài hạn - mã sẽ kéo dài một hoặc hai thập kỷ.
Giả sử một đoạn mã hầu hết không hoạt động (ổn định) trong một số năm, nhưng bây giờ cần phải thay đổi. Bạn kích hoạt lại dấu vết gỡ lỗi - nhưng thật bực bội khi phải gỡ lỗi mã gỡ lỗi (theo dõi) vì nó đề cập đến các biến đã được đổi tên hoặc thử lại, trong những năm bảo trì ổn định. Nếu trình biên dịch (bộ xử lý trước bài) luôn nhìn thấy câu lệnh in, nó đảm bảo rằng mọi thay đổi xung quanh không làm mất hiệu lực chẩn đoán. Nếu trình biên dịch không nhìn thấy câu lệnh in, nó không thể bảo vệ bạn trước sự bất cẩn của chính bạn (hoặc sự bất cẩn của đồng nghiệp hoặc cộng tác viên của bạn). Xem ' Thực hành lập trình ' của Kernighan và Pike, đặc biệt là Chương 8 (xem thêm Wikipedia về TPOP ).
Đây là "đã có, thực hiện được điều đó" - về cơ bản, tôi đã sử dụng kỹ thuật được mô tả trong các câu trả lời khác trong đó bản dựng không gỡ lỗi không thấy các câu lệnh giống như printf trong một số năm (hơn một thập kỷ). Nhưng tôi đã xem qua lời khuyên trong TPOP (xem nhận xét trước đây của tôi) và sau đó đã kích hoạt một số mã gỡ lỗi sau một số năm và gặp vấn đề về thay đổi bối cảnh phá vỡ việc gỡ lỗi. Một vài lần, việc in ấn luôn được xác nhận đã cứu tôi khỏi những vấn đề sau này.
Tôi chỉ sử dụng NDEBUG để chỉ kiểm soát các xác nhận và một macro riêng (thường là DEBUG) để kiểm soát xem theo dõi gỡ lỗi có được tích hợp vào chương trình hay không. Ngay cả khi theo dõi gỡ lỗi được tích hợp, tôi thường không muốn đầu ra gỡ lỗi xuất hiện vô điều kiện, vì vậy tôi có cơ chế kiểm soát xem đầu ra có xuất hiện không (mức gỡ lỗi và thay vì gọi fprintf()
trực tiếp, tôi gọi hàm in gỡ lỗi chỉ in có điều kiện do đó, bản dựng mã giống nhau có thể in hoặc không in dựa trên các tùy chọn chương trình). Tôi cũng có một phiên bản mã 'nhiều hệ thống con' cho các chương trình lớn hơn, để tôi có thể có các phần khác nhau của chương trình tạo ra số lượng dấu vết khác nhau - trong điều khiển thời gian chạy.
Tôi ủng hộ rằng đối với tất cả các bản dựng, trình biên dịch sẽ thấy các báo cáo chẩn đoán; tuy nhiên, trình biên dịch sẽ không tạo bất kỳ mã nào cho các câu lệnh theo dõi gỡ lỗi trừ khi bật gỡ lỗi. Về cơ bản, điều đó có nghĩa là tất cả các mã của bạn được trình biên dịch kiểm tra mỗi khi bạn biên dịch - cho dù để phát hành hay gỡ lỗi. Đây là một điều tốt!
/*
@(#)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 hỏi:
Dù sao để làm điều này để
debug_print
vẫn hoạt động ngay cả khi không có đối số? Ví dụ:debug_print("Foo");
Có một cách đơn giản, lỗi thời:
debug_print("%s\n", "Foo");
Giải pháp chỉ dành cho GCC được hiển thị bên dưới cũng cung cấp hỗ trợ cho việc đó.
Tuy nhiên, bạn có thể làm điều đó với hệ thống C99 thẳng bằng cách sử dụng:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
So với phiên bản đầu tiên, bạn mất kiểm tra giới hạn yêu cầu đối số 'fmt', điều đó có nghĩa là ai đó có thể cố gắng gọi 'debug_print ()' mà không có đối số (nhưng dấu phẩy trong danh sách đối số fprintf()
sẽ không được biên dịch) . Cho dù mất kiểm tra là một vấn đề ở tất cả là tranh cãi.
Một số trình biên dịch có thể cung cấp tiện ích mở rộng cho các cách khác để xử lý danh sách đối số có độ dài thay đổi trong macro. Cụ thể, như được ghi nhận lần đầu trong các nhận xét của Hugo Ideler , GCC cho phép bạn bỏ qua dấu phẩy thường xuất hiện sau đối số 'cố định' cuối cùng đối với macro. Nó cũng cho phép bạn sử dụng ##__VA_ARGS__
trong văn bản thay thế macro, sẽ xóa dấu phẩy trước ký hiệu if, nhưng chỉ khi, mã thông báo trước đó là dấu phẩy:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
Giải pháp này vẫn giữ được lợi ích của việc yêu cầu đối số định dạng trong khi chấp nhận đối số tùy chọn sau định dạng.
Kỹ thuật này cũng được Clang hỗ trợ để tương thích GCC.
Mục đích của việc này là
do while
gì?
Bạn muốn có thể sử dụng macro để nó trông giống như một lệnh gọi hàm, có nghĩa là nó sẽ được theo sau bởi dấu chấm phẩy. Do đó, bạn phải đóng gói cơ thể macro cho phù hợp. Nếu bạn sử dụng một if
câu lệnh mà không có xung quanh do { ... } while (0)
, bạn sẽ có:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Bây giờ, giả sử bạn viết:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
Thật không may, sự thụt lề đó không phản ánh sự kiểm soát thực tế của dòng chảy, bởi vì bộ tiền xử lý tạo ra mã tương đương với điều này (thụt lề và dấu ngoặc nhọn được thêm vào để nhấn mạnh ý nghĩa thực tế):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
Nỗ lực tiếp theo ở macro có thể là:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
Và đoạn mã tương tự bây giờ tạo ra:
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);
Và else
bây giờ là một lỗi cú pháp. Các do { ... } while(0)
tránh loop cả những vấn đề này.
Có một cách khác để viết macro có thể hoạt động:
/* BAD - BAD - BAD */
#define debug_print(...) \
((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
Điều này để lại các đoạn chương trình được hiển thị là hợp lệ. Diễn (void)
viên ngăn không cho nó được sử dụng trong bối cảnh yêu cầu một giá trị - nhưng nó có thể được sử dụng làm toán hạng bên trái của toán tử dấu phẩy nơi do { ... } while (0)
phiên bản không thể. Nếu bạn nghĩ rằng bạn sẽ có thể nhúng mã gỡ lỗi vào các biểu thức như vậy, bạn có thể thích điều này. Nếu bạn muốn yêu cầu bản in gỡ lỗi hoạt động như một câu lệnh đầy đủ, thì do { ... } while (0)
phiên bản sẽ tốt hơn. Lưu ý rằng nếu phần thân của macro liên quan đến bất kỳ dấu chấm phẩy nào (nói đại khái), thì bạn chỉ có thể sử dụng do { ... } while(0)
ký hiệu. Nó luôn hoạt động; cơ chế phát biểu biểu thức có thể khó áp dụng hơn. Bạn cũng có thể nhận được các cảnh báo từ trình biên dịch với biểu thức mà bạn muốn tránh; nó sẽ phụ thuộc vào trình biên dịch và các cờ bạn sử dụng.
TPOP trước đây tại http://plan9.bell-labs.com/cm/cs/tpop và http://cm.bell-labs.com/cm/cs/tpop nhưng cả hai đều hiện tại (2015-08-10) bị hỏng.
Nếu bạn tò mò, bạn có thể nhìn vào mã này trong GitHub trong tôi SOQ (Câu hỏi Stack Overflow) kho dưới dạng file debug.c
, debug.h
và mddebug.c
trong
src / libsoq
thư mục con.
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
, nó sẽ không biên dịch nếu bạn không có tham số printf, tức là nếu bạn chỉ gọi debug_print("Some msg\n");
Bạn có thể sửa lỗi này bằng cách sử dụng fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
## __ VA_ARGS__ cho phép không truyền tham số nào cho hàm.
#define debug_print(fmt, ...)
và #define debug_print(...)
. Đầu tiên trong số này yêu cầu ít nhất một đối số, chuỗi định dạng ( fmt
) và không hoặc nhiều đối số khác; thứ hai yêu cầu không có hoặc nhiều đối số trong tổng số. Nếu bạn sử dụng debug_print()
với cái đầu tiên, bạn sẽ gặp lỗi từ bộ tiền xử lý về việc lạm dụng macro, trong khi cái thứ hai thì không. Tuy nhiên, bạn vẫn gặp phải lỗi biên dịch vì văn bản thay thế không hợp lệ C. Vì vậy, nó thực sự không có nhiều khác biệt - do đó sử dụng thuật ngữ 'kiểm tra giới hạn'.
-D input=4,macros=9,rules=2
để đặt mức gỡ lỗi của hệ thống đầu vào thành 4, hệ thống macro thành 9 (trải qua quá trình kiểm tra nghiêm ngặt ) và hệ thống quy tắc thành 2. Có các biến thể vô tận về chủ đề; sử dụng bất cứ điều gì phù hợp với bạn.
Tôi sử dụng một cái gì đó như thế này:
#ifdef DEBUG
#define D if(1)
#else
#define D if(0)
#endif
Hơn tôi chỉ sử dụng D làm tiền tố:
D printf("x=%0.3f\n",x);
Trình biên dịch thấy mã gỡ lỗi, không có vấn đề dấu phẩy và nó hoạt động ở mọi nơi. Ngoài ra, nó hoạt động khi printf
không đủ, giả sử khi bạn phải kết xuất một mảng hoặc tính toán một số giá trị chẩn đoán là dư thừa cho chính chương trình.
EDIT: Ok, nó có thể tạo ra một vấn đề khi có else
một nơi nào đó gần đó có thể bị chặn bởi việc tiêm này if
. Đây là một phiên bản đi qua nó:
#ifdef DEBUG
#define D
#else
#define D for(;0;)
#endif
for(;0;)
, nó có thể tạo ra một vấn đề khi bạn viết một cái gì đó như D continue;
hoặc D break;
.
Để triển khai di động (ISO C90), bạn có thể sử dụng dấu ngoặc đơn, như thế này;
#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;
}
hoặc (hackish, sẽ không đề nghị nó)
#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;
}
Đây là phiên bản tôi sử dụng:
#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
Tôi sẽ làm một cái gì đó như
#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif
Tôi nghĩ rằng điều này là sạch hơn.
assert()
từ stdlib hoạt động theo cùng một cách và tôi thường chỉ sử dụng lại NDEBUG
macro cho mã gỡ lỗi của riêng mình ...
Theo http://gcc.gnu.org/onlinesocs/cpp/Variadic-Macros.html , cần phải có ##
trước __VA_ARGS__
.
Nếu không, một macro #define dbg_print(format, ...) printf(format, __VA_ARGS__)
sẽ không biên dịch ví dụ sau : dbg_print("hello world");
.
#define debug_print(FMT, ARGS...) do { \
if (DEBUG) \
fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
} while (0)
Đây là những gì tôi sử dụng:
#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/
#endif
Nó có lợi ích tốt để xử lý printf đúng cách, thậm chí không có đối số bổ sung. Trong trường hợp DBG == 0, ngay cả trình biên dịch ngu ngốc nhất cũng không có gì để nhai, do đó không có mã nào được tạo.
Yêu thích của tôi dưới đây là var_dump
, mà khi được gọi là:
var_dump("%d", count);
tạo ra sản lượng như:
patch.c:150:main(): count = 0
Tín dụng cho @ "Jonathan Leffler". Tất cả đều là C89-happy:
Mã
#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)
Vì vậy, khi sử dụng gcc, tôi thích:
#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__, __LINE__, __func__, #expr, g2rE3); g2rE3;})
Bởi vì nó có thể được chèn vào mã.
Giả sử bạn đang cố gắng gỡ lỗi
printf("%i\n", (1*2*3*4*5*6));
720
Sau đó, bạn có thể thay đổi nó thành:
printf("%i\n", DBGI(1*2*3*4*5*6));
hello.c:86:main(): 1*2*3*4*5*6->720
720
Và bạn có thể có được một phân tích về biểu thức được đánh giá là gì.
Nó được bảo vệ chống lại vấn đề đánh giá kép, nhưng sự vắng mặt của các máy phát điện sẽ khiến nó bị bỏ ngỏ cho các vụ va chạm tên.
Tuy nhiên, nó làm tổ:
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
Vì vậy, tôi nghĩ rằng miễn là bạn tránh sử dụng g2rE3 làm tên biến, bạn sẽ ổn thôi.
Chắc chắn tôi đã tìm thấy nó (và các phiên bản đồng minh cho chuỗi và phiên bản cho các mức gỡ lỗi, v.v.) là vô giá.
Tôi đã cố gắng làm thế nào để làm điều này trong nhiều năm, và cuối cùng đưa ra một giải pháp. Tuy nhiên, tôi không biết rằng đã có giải pháp khác ở đây. Đầu tiên, khác với câu trả lời của Leffler , tôi không thấy lập luận của anh ấy rằng các bản in gỡ lỗi phải luôn được biên dịch. Tôi thà không có hàng tấn mã không cần thiết thực thi trong dự án của mình, khi không cần thiết, trong trường hợp tôi cần kiểm tra và chúng có thể không được tối ưu hóa.
Không biên dịch mỗi lần nghe có vẻ tệ hơn so với thực tế. Bạn kết thúc với các bản in gỡ lỗi đôi khi không biên dịch, nhưng không quá khó để biên dịch và kiểm tra chúng trước khi hoàn thành một dự án. Với hệ thống này, nếu bạn đang sử dụng ba cấp độ gỡ lỗi, chỉ cần đặt nó ở cấp độ thông báo gỡ lỗi cấp ba, sửa lỗi biên dịch của bạn và kiểm tra bất kỳ mức nào khác trước khi bạn hoàn tất mã yer. (Vì tất nhiên, việc biên dịch các câu lệnh gỡ lỗi không đảm bảo rằng chúng vẫn hoạt động như dự định.)
Giải pháp của tôi cung cấp cho các mức độ chi tiết gỡ lỗi cũng; và nếu bạn đặt nó ở mức cao nhất, tất cả chúng sẽ biên dịch. Nếu bạn đã sử dụng mức độ chi tiết gỡ lỗi cao gần đây, tất cả chúng đều có thể biên dịch vào thời điểm đó. Cập nhật cuối cùng nên khá dễ dàng. Tôi chưa bao giờ cần nhiều hơn ba cấp độ, nhưng Jonathan nói rằng anh ta đã sử dụng chín cấp độ. Phương pháp này (như của Leffler) có thể được mở rộng đến bất kỳ số cấp nào. Việc sử dụng phương pháp của tôi có thể đơn giản hơn; chỉ cần hai câu lệnh khi được sử dụng trong mã của bạn. Tuy nhiên, tôi cũng đang mã hóa macro ĐÓNG - mặc dù nó không làm gì cả. Nó có thể nếu tôi đang gửi đến một tập tin.
Chống lại chi phí thêm bước kiểm tra họ để thấy rằng họ sẽ biên dịch trước khi giao hàng, đó là
Các chi nhánh thực sự tương đối tốn kém trong các bộ xử lý tìm nạp hiện đại. Có thể không phải là vấn đề lớn nếu ứng dụng của bạn không phải là vấn đề quan trọng về thời gian; nhưng nếu hiệu suất là một vấn đề, thì, vâng, một thỏa thuận đủ lớn mà tôi muốn chọn cho mã gỡ lỗi có phần thực thi nhanh hơn (và có thể phát hành nhanh hơn, trong trường hợp hiếm, như đã lưu ý).
Vì vậy, những gì tôi muốn là một macro in gỡ lỗi không biên dịch nếu nó không được in, nhưng thực hiện nếu nó là. Tôi cũng muốn các mức độ gỡ lỗi, vì vậy, ví dụ như nếu tôi muốn các phần quan trọng của mã không được in vào một số thời điểm, nhưng để in ở các mức khác, tôi có thể đặt mức gỡ lỗi và có thêm các bản in gỡ lỗi. đã tìm ra cách để thực hiện các mức gỡ lỗi xác định xem bản in có được biên dịch hay không. Tôi đã đạt được nó theo cách này:
// 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);
}
Để sử dụng nó, chỉ cần làm:
DEBUGLOG_INIT("afile.log");
Để ghi vào tệp nhật ký, chỉ cần làm:
DEBUGLOG_LOG(1, "the value is: %d", anint);
Để đóng nó, bạn làm:
DEBUGLOG_CLOSE();
mặc dù hiện tại điều này thậm chí không cần thiết, về mặt kỹ thuật, vì nó không làm gì cả. Tôi vẫn đang sử dụng ĐÓNG ngay bây giờ, tuy nhiên, trong trường hợp tôi thay đổi suy nghĩ về cách thức hoạt động của nó và muốn để tệp mở giữa các báo cáo ghi nhật ký.
Sau đó, khi bạn muốn bật in gỡ lỗi, chỉ cần chỉnh sửa #define đầu tiên trong tệp tiêu đề để nói, ví dụ:
#define DEBUG 1
Để báo cáo ghi nhật ký được biên dịch thành không có gì, hãy làm
#define DEBUG 0
Nếu bạn cần thông tin từ một đoạn mã được thực thi thường xuyên (nghĩa là mức độ chi tiết cao), bạn có thể muốn viết:
DEBUGLOG_LOG(3, "the value is: %d", anint);
Nếu bạn xác định DEBUG là 3, ghi nhật ký cấp 1, 2 & 3. Nếu bạn đặt nó thành 2, bạn sẽ nhận được mức ghi nhật ký 1 & 2. Nếu bạn đặt nó thành 1, bạn chỉ nhận được các báo cáo cấp 1 ghi nhật ký.
Đối với vòng lặp do-while, vì điều này đánh giá một hàm duy nhất hoặc không có gì, thay vì một câu lệnh if, vòng lặp là không cần thiết. OK, điều khiển tôi sử dụng C thay vì C ++ IO (và QString :: arg () của Qt là một cách định dạng các biến định dạng an toàn hơn khi ở Qt, cũng khá lắt léo, nhưng mất nhiều mã hơn và tài liệu định dạng không được tổ chức như có thể - nhưng tôi vẫn tìm thấy các trường hợp thích hợp hơn), nhưng bạn có thể đặt bất kỳ mã nào vào tệp .cpp bạn muốn. Nó cũng có thể là một lớp, nhưng sau đó bạn sẽ cần phải khởi tạo nó và theo kịp nó, hoặc làm một () mới và lưu trữ nó. Bằng cách này, bạn chỉ cần thả các câu lệnh #incoide, init và đóng tùy ý vào nguồn của mình và bạn đã sẵn sàng để bắt đầu sử dụng nó. Nó sẽ làm cho một lớp tốt, tuy nhiên, nếu bạn rất nghiêng.
Trước đây tôi đã thấy rất nhiều giải pháp, nhưng không có giải pháp nào phù hợp với tiêu chí của tôi cũng như giải pháp này.
Không có ý nghĩa khủng khiếp, nhưng ngoài ra:
DEBUGLOG_LOG(3, "got here!");
); do đó cho phép bạn sử dụng, ví dụ định dạng .arg () an toàn hơn của Qt. Nó hoạt động trên MSVC, và do đó, có thể là gcc. Nó sử dụng ##
trong #define
s, không chuẩn, như Leffler chỉ ra, nhưng được hỗ trợ rộng rãi. (Bạn có thể mã hóa lại nó không sử dụng ##
nếu cần thiết, nhưng bạn sẽ phải sử dụng một bản hack như anh ta cung cấp.)Cảnh báo: Nếu bạn quên cung cấp đối số mức ghi nhật ký, MSVC sẽ vô tình tuyên bố định danh không được xác định.
Bạn có thể muốn sử dụng tên biểu tượng tiền xử lý khác với DEBUG, vì một số nguồn cũng định nghĩa biểu tượng đó (ví dụ: pross sử dụng ./configure
các lệnh để chuẩn bị xây dựng). Nó dường như tự nhiên với tôi khi tôi phát triển nó. Tôi đã phát triển nó trong một ứng dụng mà DLL đang được sử dụng bởi một thứ khác và nó được quy ước nhiều hơn để gửi bản in nhật ký đến một tệp; nhưng thay đổi nó thành vprintf () cũng sẽ hoạt động tốt.
Tôi hy vọng điều này sẽ cứu nhiều bạn đau buồn về việc tìm ra cách tốt nhất để ghi nhật ký gỡ lỗi; hoặc cho bạn thấy một trong những bạn có thể thích. Tôi đã nửa vời cố gắng tìm ra điều này trong nhiều thập kỷ. Hoạt động trong MSVC 2012 & 2015, và do đó có thể trên gcc; cũng như có thể làm việc trên nhiều người khác, nhưng tôi đã không thử nó trên chúng.
Tôi có nghĩa là để thực hiện một phiên bản phát trực tuyến của một ngày này, quá.
Lưu ý: Cảm ơn Leffler, người đã giúp tôi định dạng tin nhắn của tôi tốt hơn cho StackOverflow.
if (DEBUG)
câu lệnh trong thời gian chạy, không được tối ưu hóa" - vốn đang nghiêng về cối xay gió . Toàn bộ điểm của hệ thống tôi mô tả là mã được kiểm tra bởi trình biên dịch (quan trọng và tự động - không cần xây dựng đặc biệt) nhưng mã gỡ lỗi hoàn toàn không được tạo vì nó được tối ưu hóa (do đó không có tác động thời gian chạy nào kích thước mã hoặc hiệu suất vì mã không có trong thời gian chạy).
((void)0)
- thật dễ dàng.
Tôi tin rằng biến thể của chủ đề này cung cấp các danh mục gỡ lỗi mà không cần phải có tên macro riêng cho mỗi danh mục.
Tôi đã sử dụng biến thể này trong một dự án Arduino trong đó không gian chương trình bị giới hạn ở 32K và bộ nhớ động bị giới hạn ở mức 2K. Việc bổ sung các câu lệnh gỡ lỗi và chuỗi gỡ lỗi theo dõi nhanh chóng sử dụng không gian. Vì vậy, điều cần thiết là có thể giới hạn dấu vết gỡ lỗi được bao gồm tại thời điểm biên dịch đến mức tối thiểu cần thiết mỗi khi mã được xây dựng.
#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");
...