__FILE__:记录文件的路径加名称
__LINE__:记录文件已经被编译的行数
__DATE__:记录文件的编译日期
__TIME__:记录文件的编译时间
可以当作变量直接使用,一般用作程序调试
例子:
#include <iostream>
using namespace std;
int main()
{
cout << "File = " << __FILE__ << '\n'
<< "LINE = " << __LINE__ << '\n'
<< "DATE = " << __DATE__ << '\n'
<< "TIME = " << __TIME__
<< endl;
getchar();
}
运行结果:
File = G:/program/study/c++/test1.cpp
LINE = 17
DATE = May 27 2004
TIME = 09:59:01
利用__FILE__,__LINE__,FUNCTION__实现代码跟踪调试
(linux下c语言编程 )先看下简单的初始代码:注意其编译运行后的结果。
root@xuanfei-desktop:~/cpropram/2# cat global.h //头文件
#ifndef CLOBAL_H
#define GLOBAL_H
#include <stdio.h>
int funca(void);
int funcb(void);
#endif
root@xuanfei-desktop:~/cpropram/2# cat funca.c //函数a
#include "global.h"
int funca(void)
{
printf ("this is function\n");
return 0;
}
root@xuanfei-desktop:~/cpropram/2# cat funcb.c //函数b
#include "global.h"
int funcb(void)
{
printf ("this is function\n");
return 0;
}
root@xuanfei-desktop:~/cpropram/2# gcc -Wall funca.c funcb.c main.c //联合编译
root@xuanfei-desktop:~/cpropram/2# ./a.out //运行
this is main
this is function
this is main
this is function
this is main
相同结果很难让人看出那里出错,下面我们用用 __FILE__,__LINE__,__FUNCTION__加入代码,看看有什么区别吗.
把 __FILE__,__LINE__,__FUNCTION__加入到mail.c中
root@xuanfei-desktop:~/cpropram/2# cat main.c
#include "global.h"
int main(int argc, char **argv)
{
printf("%s(%d)-%s: this is main\n",__FILE__,__LINE__,__FUNCTION__);
funca();
printf("%s(%d)-%s: this is main\n",__FILE__,__LINE__,__FUNCTION__);
funcb();
printf("%s(%d)-%s: this is main\n",__FILE__,__LINE__,__FUNCTION__);
return 0;
}
root@xuanfei-desktop:~/cpropram/2# gcc -Wall funca.c funcb.c main.c
root@xuanfei-desktop:~/cpropram/2# ./a.out
main.c(4)-main: this is main
this is function
main.c(6)-main: this is main
this is function
main.c(8)-main: this is main
上面的结果main.c(4)-main:this is main 表示在mian.c源代码的第四行main函数里边打印出来的 this is main
那样的话就很方便的让程序员对自己的程序进行排错!
为了更方便的使用它我们可以通过在global.h代码中进行宏定义
root@xuanfei-desktop:~/cpropram/2# cat global.h
#ifndef CLOBAL_H
#define GLOBAL_H
#include <stdio.h>
int funca(void);
int funcb(void);
#define DEBUGFMT "%s(%d)-%s"
#define DEBUGARGS __FILE__,__LINE__,__FUNCTION__
#endif
root@xuanfei-desktop:~/cpropram/2# cat funca.c
#include "global.h"
int funca(void)
{
printf (DEBUGFMT " this is function\n",DEBUGARGS);
return 0;
}
root@xuanfei-desktop:~/cpropram/2# cat funcb.c
#include "global.h"
int funcb(void)
{
printf (DEBUGFMT " this is function\n",DEBUGARGS);
return 0;
}
root@xuanfei-desktop:~/cpropram/2# cat main.c
#include "global.h"
int main(int argc, char **argv)
{
printf(DEBUGFMT "this is main\n", DEBUGARGS);
funca();
printf(DEBUGFMT "this is main\n", DEBUGARGS);
funcb();
printf(DEBUGFMT "this is main\n", DEBUGARGS);
return 0;
}
root@xuanfei-desktop:~/cpropram/2# gcc -Wall funca.c funcb.c main.c
root@xuanfei-desktop:~/cpropram/2# ./a.out
main.c(4)-mainthis is main
funca.c(4)-funca this is function
main.c(6)-mainthis is main
funcb.c(4)-funcb this is function
main.c(8)-mainthis is main
root@xuanfei-desktop:~/cpropram/2#
这就是通过定义__FILE__,__LINE__,FUNCTION__的宏来简单实现代码的跟踪调试:)
下面是一个可供调试用的头文件
#ifndef _GOLD_DEBUG_H
#define _GOLD_DEBUG_H
#ifdef __cplusplus
#if __cplusplus
extern "C"{
#endif
#endif /* __cplusplus */
//#define GI_DEBUG
#ifdef GI_DEBUG
#define GI_DEBUG_POINT() printf("\n\n[File:%s Line:%d] Fun:%s\n\n", __FILE__, __LINE__, __FUNCTION__)
#define dbg_printf(arg...) printf(arg);
#define GI_ASSERT(expr) \
do{ \
if (!(expr)) { \
printf("\nASSERT failed at:\n >File name: %s\n >Function : %s\n >Line No. : %d\n >Condition: %s\n", \
__FILE__,__FUNCTION__, __LINE__, #expr);\
} \
}while(0);
/*调试宏, 用于暂停*/
#define GI_DEBUG_PAUSE() \
do \
{ \
GI_DEBUG_POINT(); \
printf("pause for debug, press 'q' to exit!\n"); \
char c; \
while( ( c = getchar() ) ) \
{ \
if('q' == c) \
{ \
getchar(); \
break; \
} \
} \
}while(0);
#define GI_DEBUG_PAUSE_ARG(arg...) \
do \
{ \
printf(arg); \
GI_DEBUG_PAUSE() \
}while(0);
#define GI_DEBUG_ASSERT(expression) \
if(!(expression)) \
{ \
printf("[ASSERT],%s,%s:%d\n", __FILE__, __FUNCTION__, __LINE__);\
exit(-1); \
}
#else
#define GI_ASSERT(expr)
#define GI_DEBUG_PAUSE()
#define GI_DEBUG_PAUSE_ARG(arg...)
#define GI_DEBUG_POINT()
#define dbg_printf(arg...)
#define GI_DEBUG_ASSERT(expression)
#endif
#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */
#endif
作为一个Linux系统下的C程序员,你可能发现调试程序是个比较麻烦的工作,虽然已经有gdb,kgdb等专业的调试软件,但如果对这些软件运用不熟练是根本达不到调试程序找出bug的目的的。又或者你对gdb已经很熟了,但运行gdb开始调试后在哪里设置断点成了你头痛的问题?当然,你可以从程序开始就以单步运行step by step来调试程序,但这会耗去你很多时间。
如果你能很好地跟踪并记录程序的运行情况,那么一切将变得简单。下面我以一个实例说明我是如何操作的:
首先我有一个程序主体main,其代码如下:
//trace.c 开始///
/*********************************************************************
*filename: trace.c
*purpose: demonstrate how to trace program easily. We'll
* get every source filename, function-name, and the
* current line number wrote into a stream. The stream
* can be a file descriptor or stdout
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*date time:2005-11-30 00:30
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
#include "global.h"
#include "MyFuncOne.h"
#include "MyFuncTwo.h"
int x;
int main(int argc, char ** argv)
{
x = 5;
dump(stdout, "now: x=%d", x);
MyFuncOne();
MyFuncTwo();
dump(stdout, "now: x=%d", x);
return 0;
}
//trace.c 结束///
这个main里面引用了global.h,MyFuncOne.h,MyFuncTwo.h等,global.h里面是一个宏定义,定义了dump宏,其内容如下:
//global.h 开始///
#ifndef GLOBAL_H
#define GLOBAL_H
#include "dump.h"
/*********************************************************************
*filename: global.h
*purpose: dump function declare
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*date time:2005-11-30 00:30
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
#ifdef DEBUG
#define dump(fp, x...) debug_print(fp, __FILE__, __LINE__, __FUNCTION__, ##x);
#else
#define dump(fp, x...)
#endif
#endif
//global.h 结束///
global.h这里又引用了dump.h,其内容稍后再贴出来。
MyFuncOne.h和MyFuncTwo.h分别定义了两个函数MyFuncOne和MyFuncTwo,其内容如下:
//MyFuncOne.h 开始///
#ifndef MYFUNC_ONE_H
#define MYFUNC_ONE_H
#include "global.h"
/*********************************************************************
*filename: MyFuncOne.h
*purpose: MyFuncOne function declare
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*date time:2005-11-30 00:30
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
void MyFuncOne();
#endif
//MyFuncOne.h 结束///
//MyFuncTwo.h 开始///
#ifndef MYFUNC_TWO_H
#define MYFUNC_TWO_H
#include "global.h"
/*********************************************************************
*filename: MyFuncTwo.h
*purpose: MyFuncTwo function declare
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
void MyFuncTwo();
#endif
//MyFuncTwo.h 结束///
//MyFuncOne.c 开始///
#include "MyFuncOne.h"
extern int x;
/*********************************************************************
*filename: MyFuncOne.c
*purpose: MyFuncOne function instance
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*date time:2005-11-30 00:30
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
void MyFuncOne()
{
x *= -2;
dump(stdout, "MyFuncOne, now: x=%d", x);
}
//MyFuncOne.c 结束///
//MyFuncTwo.c 开始///
#include "MyFuncTwo.h"
extern int x;
/*********************************************************************
*filename: MyFuncTwo.h
*purpose: MyFuncOne function declare
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*date time:2005-11-30 00:30
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
void MyFuncTwo()
{
x++;
dump(stdout, "MyFuncTwo, now: x=%d", x);
}
//MyFuncTwo.c 结束///
现在该是时候说dump了,先看看其实现:
//dump.h 开始///
#ifndef DUMP_H
#define DUMP_H
#include <sys/param.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
/*********************************************************************
*filename: dump.h
*purpose: debug_print function declare
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*date time:2005-11-30 00:30
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
void debug_print(FILE * fp, const char * filename, const int line, const char * funcname, char *fmt, ...);
#endif
//dump.h 结束///
//dump.c 开始///
#include "dump.h"
/*********************************************************************
*filename: dump.c
*purpose: debug_print function instance
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
*date time:2005-11-30 00:30
*Note: 任何人可以任意复制代码并运用这些代码,当然包括你的商业用途
* 但请遵循GPL
*********************************************************************/
void debug_print(FILE * fp, const char * filename, const int line, const char * funcname, char *fmt, ...)
{
char buf[1024];
time_t t;
struct tm * now;
va_list ap;
time(&t);
now = localtime(&t);
va_start(ap, fmt);
fprintf(fp, "%04d-%02d-%02d %02d:%02d:%02d -- %s(%d):%s DEBUG:@\"", now -> tm_year + 1900, now -> tm_mon + 1, now -> tm_mday, now -> tm_hour, now -> tm_min, now -> tm_sec, filename, line, funcname);
vsprintf(buf, fmt, ap);
fprintf(fp, "%s\"@\n", buf);
va_end(ap);
}
//dump.c 结束///
大家一定注意到:这个程序和大家一般写的程序并无大的区别,除了__FILE__, __LINE__, __FUNCTION__等几个宏和dump宏,__FILE__, __LINE__, __FUNCTION__是编译的时候已经内置了的几个宏,用来表明当前程序运行到了哪个源文件的哪一行,同时表明当前在哪个函数里面。而我们定义一个dump宏就是用来把这些信息送到一个文件句柄去。比如你的日志文件。这样,在任何程序需要的地方都可以加上一句dump来把需要的调试信息记录下来。
比如编译上述程序:
gcc -DDEBUG trace.c dump.c MyFuncOne.c MyFuncTwo.c -o trace
然后运行程序可以得到如下结果:
2005-11-30 00:40:38 -- trace.c(22):main DEBUG:@"now: x=5"@
2005-11-30 00:40:38 -- MyFuncOne.c(15):MyFuncOne DEBUG:@"MyFuncOne, now: x=-10"@
2005-11-30 00:40:38 -- MyFuncTwo.c(15):MyFuncTwo DEBUG:@"MyFuncTwo, now: x=-9"@
2005-11-30 00:40:38 -- trace.c(25):main DEBUG:@"now: x=-9"@
第一行:显示在trace.c源文件的第22行处,即main函数内打印出:now: x=5;
第二行:显示在MyFuncOne.c源文件的第15行处,即MyFuncOne函数内打印出:MyFuncOne, now: x=-10;
第三行:显示在MyFuncTwo.c源文件的第15行处,即MyFuncTwo函数内打印出:MyFuncTwo, now: x=-9;
第四行:显示在trace.c源文件的第25行处,即main函数内打印出:now: x=-9;
如果程序加多点dump,则程序运行到了哪里出了问题就会一目了然了。
在C语言中以编程的方式获取函数名
仅仅为了获取函数名,就在函数体中嵌入硬编码的字符串,这种方法单调乏味还易导致错误,不如看一下怎样使用新的C99特性,在程序运行时获取函数名吧。
对象反射库、调试工具及代码分析器,经常会需要在运行时访问函数的名称,直到不久前,唯一能完成此项任务并且可移植的方法,是手工在函数体内嵌入一个带有该函数名的硬编码字符串,不必说,这种方法非常单调无奇,并且容易导致错误。本文将要演示怎样使用新的C99特性,在运行时获取函数名。
那么怎样以编程的方式从当前运行的函数中得到函数名呢?
答案是:使用__FUNCTION__ 及相关宏。
引出问题
通常,在调试中最让人心烦的阶段,是不断地检查是否已调用了特定的函数。对此问题的解决方法,一般是添加一个cout或printf()——如果你使用C语言,如下所示:
void myfunc() { cout<<"myfunc()"<<endl; //其他代码 } |
通常在一个典型的工程中,会包含有数千个函数,要在每个函数中都加入一条这样的输出语句,无疑难过上“蜀山”啊,因此,需要有一种机制,可以自动地完成这项操作。
获取函数名
作为一个C++程序员,可能经常遇到 __TIME__、__FILE__、__DATE__ 这样的宏,它们会在编译时,分别转换为包含编译时间、处理的转换单元名称及当前时间的字符串。
在最新的ISO C标准中,如大家所知的C99,加入了另一个有用的、类似宏的表达式__func__,其会报告未修饰过的(也就是未裁剪过的)、正在被访问的函数名。请注意,__func__不是一个宏,因为预处理器对此函数一无所知;相反,它是作为一个隐式声明的常量字符数组实现的:
static const char __func__[] = "function-name"; |
在function-name处,为实际的函数名。为激活此特性,某些编译器需要使用特定的编译标志,请查看相应的编译器文档,以获取具体的资料。
有了它,我们可免去大多数通过手工修改,来显示函数名的苦差事,以上的例子可如下所示进行重写:
void myfunc() { cout<<"__FUNCTION__"<<endl; } |
官方C99标准为此目的定义的__func__标识符,确实值得大家关注,然而,ISO C++却不完全支持所有的C99扩展,因此,大多数的编译器提供商都使用 __FUNCTION__ 取而代之,而 __FUNCTION__ 通常是一个定义为 __func__ 的宏,之所以使用这个名字,是因为它已受到了大多数的广泛支持。
在Visual Studio 2005中,默认情况下,此特性是激活的,但不能与/EP和/P编译选项同时使用。请注意在IDE环境中,不能识别__func__ ,而要用__FUNCTION__ 代替。
Comeau的用户也应使用 __FUNCTION__ ,而不是 __func__ 。
C++ BuilderX的用户则应使用稍稍不同的名字:__FUNC__ 。
GCC 3.0及更高的版本同时支持 __func__ 和__FUNCTION__ 。
一旦可自动获取当前函数名,你可以定义一个如下所示显示任何函数名的函数:
void show_name(const char * name) { cout<<name<<endl; } void myfunc() { show_name(__FUNCTION__); //输出:myfunc } void foo() { show_name(__FUNCTION__); //输出:foo } |
因为 __FUNCTION__ 会在函数大括号开始之后就立即初始化,所以,foo()及myfunc()函数可在参数列表中安全地使用它,而不用担心重载。
签名与修饰名
__FUNCTION__ 特性最初是为C语言设计的,然而,C++程序员也会经常需要有关他们函数的额外信息,在Visual Studio 2005中,还支持另外两种非标准的扩展特性:__FUNCDNAME__ 与 __FUNCSIG__ ,其分别转译为一个函数的修饰名与签名。函数的修饰名非常有用,例如,在你想要检查两个编译器是否共享同样的ABI时,就可派得上用场,另外,它还能帮助你破解那些含义模糊的链接错误,甚至还可用它从一个DLL中调用另一个用C++链接的函数。在下例中,show_name()报告了函数的修饰名:
void myfunc() { show_name(__FUNCDNAME__); //输出:?myfunc@@YAXXZ } |
一个函数的签名由函数名、参数列表、返回类型、内含的命名空间组成。如果它是一个成员函数,它的类名和const/volatile限定符也将是签名的一部分。以下的代码演示了一个独立的函数与一个const成员函数签名间的不同之处,两个函数的名称、返回类型、参数完全相同:
void myfunc() { show_name(__FUNCSIG__); // void __cdecl myfunc(void) } struct S { void myfunc() const { show_name(__FUNCSIG__); //void __thiscall S::myfunc(void) const } }; |