调试常用的 __FILE__, __FUNCTION__, __LINE__
调试常用的 __FILE__, __FUNCTION__, __LINE__ 没想到 VC6 不支持 __FUNCTION__ 所以我写了如下的奇怪代码 //用来记录当前行和当前函数//也可说是记录 堆栈 //当然还要对 __FUNCTION__ 宏作点修饰,因为这个宏只是在函数里面才起作用 #define DEBUG_NEW_HOOK #ifdef DEBUG_NEW_HOOK #endif 背景知识: -------------------------------------------------- http://www.sina.com.cn 2006年07月26日 09:03 天极yesky 作者:谢启东编译 仅仅为了获取函数名,就在函数体中嵌入硬编码的字符串,这种方法单调乏味还易导致错误,不如看一下怎样使用新的C99特性,在程序运行时获取函数名吧。 对象反射库、调试工具及代码分析器,经常会需要在运行时访问函数的名称,直到不久前,唯一能完成此项任务并且可移植的方法,是手工在函数体内嵌入一个带有该函数名的硬编码字符串,不必说,这种方法非常单调无奇,并且容易导致错误。本文将要演示怎样使用新的C99特性,在运行时获取函数名。 那么怎样以编程的方式从当前运行的函数中得到函数名呢? 答案是:使用__FUNCTION__ 及相关宏。 引出问题 通常,在调试中最让人心烦的阶段,是不断地检查是否已调用了特定的函数。对此问题的解决方法,一般是添加一个cout或printf()——如果你使用C语言,如下所示: 通常在一个典型的工程中,会包含有数千个函数,要在每个函数中都加入一条这样的输出语句,无疑难过上“蜀山”啊,因此,需要有一种机制,可以自动地完成这项操作。 获取函数名 作为一个C++程序员,可能经常遇到 __TIME__、__FILE__、__DATE__ 这样的宏,它们会在编译时,分别转换为包含编译时间、处理的转换单元名称及当前时间的字符串。 在最新的ISO C标准中,如大家所知的C99,加入了另一个有用的、类似宏的表达式__func__,其会报告未修饰过的(也就是未裁剪过的)、正在被访问的函数名。请注意,__func__不是一个宏,因为预处理器对此函数一无所知;相反,它是作为一个隐式声明的常量字符数组实现的: 在function-name处,为实际的函数名。为激活此特性,某些编译器需要使用特定的编译标志,请查看相应的编译器文档,以获取具体的资料。 有了它,我们可免去大多数通过手工修改,来显示函数名的苦差事,以上的例子可如下所示进行重写: 官方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 myfunc() void foo() 因为 __FUNCTION__ 会在函数大括号开始之后就立即初始化,所以,foo()及myfunc()函数可在参数列表中安全地使用它,而不用担心重载。 签名与修饰名 __FUNCTION__ 特性最初是为C语言设计的,然而,C++程序员也会经常需要有关他们函数的额外信息,在Visual Studio 2005中,还支持另外两种非标准的扩展特性:__FUNCDNAME__ 与 __FUNCSIG__ ,其分别转译为一个函数的修饰名与签名。函数的修饰名非常有用,例如,在你想要检查两个编译器是否共享同样的ABI时,就可派得上用场,另外,它还能帮助你破解那些含义模糊的链接错误,甚至还可用它从一个DLL中调用另一个用C++链接的函数。在下例中,show_name()报告了函数的修饰名: 一个函数的签名由函数名、参数列表、返回类型、内含的命名空间组成。如果它是一个成员函数,它的类名和const/volatile限定符也将是签名的一部分。以下的代码演示了一个独立的函数与一个const成员函数签名间的不同之处,两个函数的名称、返回类型、参数完全相同: struct S
发表:我是马甲 时间:2007-8-9 13:44 |
||
再转一个超级牛的文章,我都没怎么看懂 :) 留以后研究吧. -------------------------------------------------- 错误处理 | 闲来无事作点翻译工作,今天要介绍的是关于错误处理的.以下内容大部分不是我的原创,我只是把他们收集到一起来了而已. 错误处理在一个系统里面算是一个比较底层的东西了.拥有一个稳定的错误处理系统,是一个良好的系统的基础.从发展的角度看,错误处理大体有下面几种方式. 比较基础的,使用返回值表示错误还是正确,比如使用int作为返回值,0表示正常1表示错误,这种算是c语言里面的办法了,比如windows的api都是使用这种方式进行错误信息的传递.很明显有的时候光是一个32位的值实在表示不了太复杂的东西,这个时候各个有各个的实现方法,比如windows使用的GetLastError函数.com也使用这种方式,大部分的函数都返回一个HRESULT,他目前是一个typedef,其实是一个32位的long,这个long被分成了好几个部分,每个部分表示不同的意义,这个可以在msdn或者是windows的头文件里面找到解释.这种方式是比较明显的错误传递方式.但是缺点也是很显然的.因为返回值用来传递错误信息,所以函数本身的信息返回就要使用其他的方式,c语言里面只能使用传地址的方式了,这个在windows的api里面也经常看到,另外,错误信息是考返回值传递的,所以错误的检查必须要调用者来完成,就得写下比if-else这样的测试语句,而且如果露掉了这样的语句就很可能发生想不到的事情,而且必须是层层返回错误信息,这样的方式不仅仅在程序实现本身上面,而且在整个代码的可读性上面都有很大损失.但是在c语言里面,这也是没有办法的办法.windows对这个问题也提供了一种解决方案,seh--结构话异常处理,说他能完全的处理错误也不尽然,他面向的不是程序语义方面的错误,而是程序的bug,比如说,我的一段程序要打开一个文件,但是这个文件由于某些原因损坏了,这个属于程序本身应该发现纠正的错误,而不是windows来完成的任务.windows只是捕获那些诸如内存访问非法,除0一类的错误,(当然你可以自己调用RaiseException来达到同样的目的).seh看起来像下面的样子: windows的seh实现上面的细节可以参考msj的under the hood的一篇叫A Crash Course on the Depths of Win32 Structured Exception Handling.的文章,上面对windows的seh有比较详细的讲解,简单的说就是windows在每个线程的tib(thread information block)里面保存了一个链表,这个链表里面放了些发生异常的时候windows要调用的应用程序注册的回调函数,当异常发生的时候windows从链表的开头调用那些回调函数,回调函数返回适当的值表示自己是否处理了这个异常,如果没有,则windows移动的下一个链表节点,如果到了链表的结尾都没有人能处理这个异常,windows会转到一个默认的函数,这个函数就会在屏幕上面显示一个大家都应该见过的筐---应用程序发生了一个错误,将要关闭,同时有一个详细信息的按钮. 在c++里面可能就不太会使用到这种返回值的方式了,我个人认为c++的程序员优先考虑的应该是异常,虽然很多人很排斥这个新的东西,c++的异常机制把程序员从小心检查返回值的地方解救出来,你再不用去检查函数的返回值(在以前是必须的,不管你关心不关系你调用的函数的返回值,你都必须要去检查,因为你有责任把这个返回值返回给调用你的人),在c++的异常机制的帮助下,你可以随意的写代码,而不用去管函数的反复值,所以的错误都应该被最能处理的人处理,那些不想处理错误也不能处理的函数就能当错误不存在一样.必须下面的代码段 void AFunctionMayMeetSomeError() void AFunctionDoNotCare() void AFunctionWouldDealWithTheError() 利用try-catch结构能比较大的简化错误的处理的方式,我个人任务应该是很有用的东西,不过使用try-catch会带来额外的开销,这个开销主要是体现在代码的长度加大,运行的速度都没有什么太大的影响(这个可以从编译器的实现代码上面看出来,但是很多反对异常的人都任务他会降低运行速度.呵呵) 作为c++的程序员,现在我们有了一个比较有力的错误处理工具,现在问题又来了,对于程序预料中的异常,是比较能处理的,对于那些程序中预料不到的异常,我们希望获得更加详细的信息,比如函数的调用堆栈,位于源代码的文件行数等等,更甚至,我们想知道当异常发生的时候,我们的程序的具体信息,局部变量,全局变量的值,然后我们可能对此产生一个crash.log文件,要用户返回这个log文件我们加以分析查找bug等等.这个时候c++的异常能作的事情就非常的少了.像源代码文件名行数这些信息我们还可以利用__FILE__,__LINE__,__FUNCTION__这样的编译的宏来获取到,但是其他的就不太可能依赖c++语言本身的东西了,这个时候你也许就要求助于seh,因为windows在异常发生的时候会准备足够的信息,然后调用我们注册的异常处理函数,在这些信息里面,你就能找到你想要的东西. LPCTSTR what() const 然后我们定义下面的宏 这样我们的异常类里面就包含我们要的文件名,函数名,源代码行的信息了,使用vc的时候你还能使用一点小技巧,如果你把这个异常信息输出到vc的debug的output窗口的时候,能双击定位到发生异常的地方,就像你在编译的时候出的错误一样,双击就能定位到错误位置,方法很简单,你使用 "文件名字(行号)"的格式输出就ok了. 但是我们并不满足这么一点小小的提示信息,我们需要更多的信息.这个时候,我们得借助windows的seh了.在异常发生的时候windows会调用到你设置的回调函数(这个函数并不是你自己设置的,而是编译器完成的,vc编译c++的try结构的时候设置的函数名字叫__CxxFrameHandler,编译__try结构的时候设置的是_exception_handle3),而c++的异常已经江朗才尽了,我们看看__try结构的时候,编译器都干了什么,编译器会执行我们写在__except后面括号里面的内容,在这个括号里面我们可以调用2个函数GetExceptionInformation()和GetExceptionCode(),这个两个函数能返回我们要的信息,注意,这两个函数只能在__except后面的括号里面调用.在这个括号里面还可以调用我们自己的函数,上面两个函数的返回值是能当作参数传递的,很明显,我们利用这个性质就能作很多的事情了.必须注意我们的函数必须要返回几个固定的值,来告诉windows这个异常我们处理还是不处理,我们建立下面的结构 __try 真正作事情的是CrashFilter函数,这个函数里面我们就能为所欲为了. 从CONTEXT里面获取到eip,从而定位到发生异常的模块(使用VirtualQuery先获取到这个内存地址(eip)所位于的内存块,然后利用这个内存块的起始地址调用GetModuleFileName就能获取到模块的名字,这个方面可以参考其他很多的例子,到google上面搜索怎样获取内存里的模块列表就能找到详细的方法).有了eip,我们还能读取到异常指令的内容,直接使用eip的值读就ok(因为windows使用的是flat地址模式),然后利用异常代码(可以从EXCEPTION_POINTERS里面获取也可以利用GetExceptionCode()来得到)来获取异常的信息,这个信息大部分能从msdn里面查找到,其他的可以在ntdll.dll里面去获取调(调用FormatMessage函数,指定ntdll.dll的模块句柄),然后也许你要收集目标计算机的cpu类型,内存状态,操作系统信息等等(这些能通过GetSystemInfo,GlobalMemoryStatus,GetVersionEx函数来获取,这些都能在google上面搜索到详细的方法).然后你可能会dump堆栈,这个时候有个小技巧了,win32下面线程的TIB总是放到fs指定的段里面而fs:[4]这个地方放的就是栈的top地址,而当前栈的地址在CONTEXT里面有记录,你要作的就是把context的esp指针到fs:[4]之间的内存全部dump出来就ok.然后也许你要列出当前进程里面的全部dll名字,和dll的信息,这个也落在VirtualQuery函数上面,基本的方法就是遍历4G的虚拟地址空间,反复的调用VirtualQuery函数,一旦发现是合法的内存地址空间就调用GetModuleFileName函数如果成功了就表示是一个dll,这个时候你就能获取到dll的dos文件头,进一步获取到nt文件头,接着获取到dll的全部...你要知道就是一个dll和exe的module handle其实是dll和exe文件在内存里面的开始的地址,而从这个地址开始的就dll和exe文件的dos文件头. 有了这些东西其实也很无趣,你dump出来的东西要么用处不大,要么就是实在没有办法读取的信息,那些16进制的stack内容实在用处不大.接下来的东西就有点激动人心了,我们要dump出异常发生的时候函数的调用堆栈,dump出函数的局部变量,全局变量的值. 这个艰巨的任务就落到了windows的debug api上面了,windows在nt以后的版本发行了一个叫dbghelp.dll的文件,这个文件就能完成我们要求的内容.具体的信息可以查看msdn,我下面要说的内容也能在msj里面的Under the Hood: Improved Error Reporting with DBGHELP 5.1一文中找到.注意要完成下面的内容你必须要有exe文件或者dll文件的pdb文件.vc会帮您产生这个文件的,他就是调试用的符号文件. 关键部分在于5.1里面的几个新的函数,我们就能获取到这些想要的东西.这个文章不是讲解怎么使用dbghelp的文章,所以我跳过了他的使用方法.具体的可以到google上面搜索,或者查看msdn. 想要dump出call stack,必然要查看stack,这个任务交给dbghelp lib的StackWalk函数完成,你要准备一个STACKFRAME结构,传递给StackWalk函数,其他的几个参数都是很容易获取到的,机器类型,进程句柄,线程句柄,context(刚刚的那个context就能拿来使用),两个回调函数(不用自己实现,使用dbghelp lib自己实现好的函数),然后StackWalk就填充好你传递的STACKFRAME结构,接下来你就利用这个结构调用SymFromAddr这个函数就能获取到当前栈位置的函数名字,同时还有当前pc位置相对于函数开始代码pc的偏移量.调用SymGetLineFromAddr函数获取源代码文件名和行的信息.这样就能完成call stack的处理过程, // 初始化stackframe结构 // 检查帧的正确性 // 正在调用的函数名字 // 偏移量 // 获取符号 IMAGEHLP_STACK_FRAME imagehlpStackFrame; 唯一你要设置的就是那个地址,简单的传递刚刚的stack frame的pc的offset就ok.切记这个值的不同,你获取的信息就可能不同. BOOL CALLBACK EnumerateSymbolsCallback(PSYMBOL_INFO pSymInfo,ULONG SymbolSize,PVOID UserContext) 第一个就是符号的信息,你利用这个信息来获取你要要的结构,第二个是大小,基本可以忽略,最后一个是符号的context,紧记局部变量都是context向关的,都是使用[ebp-??]这样的来访问的. 到这里你已经获得了足够的信息了....整个事情就都完成了. 嗯,上面的步骤我自己都感觉是自己写个看得懂的人看的[email protected] 呵呵.要看懂上面的内容呢,你要有基本的汇编知识,要知道c语言编译器是大致上怎么工作的,有了这个基础,再了解一点windows系统的稍微底层一点知识再在msdn的帮助下面就能实现自己的crash dump函数了. 推荐几个文章,高手的文章一定比我写的好,我的这种东西不登大雅之堂的,让大家见笑了. 然后是来自msj的两个under the hood(专栏作者超牛...) Improved Error Reporting with DBGHELP 5.1 APIs 我已经把这些种技术包含到了自己的新工程里面了,一个字---超级爽.... |
__FILE__,__LINE__,FUNCTION__实现代码跟踪调试(linux下c语言编程 )先看下简单的初始代码:注意其编译运行后的结果。
[email protected]:~/cpropram/2# cat global.h //头文件
#ifndef CLOBAL_H
#define GLOBAL_H
#include <stdio.h>
int funca(void);
int funcb(void);
#endif
[email protected]:~/cpropram/2# cat funca.c //函数a
#include "global.h"
int funca(void)
{
printf ("this is function\n");
return 0;
}
[email protected]:~/cpropram/2# cat funcb.c //函数b
#include "global.h"
int funcb(void)
{
printf ("this is function\n");
return 0;
}
[email protected]:~/cpropram/2# gcc -Wall funca.c funcb.c main.c //联合编译
[email protected]:~/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中
[email protected]:~/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;
}
[email protected]:~/cpropram/2# gcc -Wall funca.c funcb.c main.c
[email protected]:~/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代码中进行宏定义
[email protected]:~/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
[email protected]:~/cpropram/2# cat funca.c
#include "global.h"
int funca(void)
{
printf (DEBUGFMT " this is function\n",DEBUGARGS);
return 0;
}
[email protected]:~/cpropram/2# cat funcb.c
#include "global.h"
int funcb(void)
{
printf (DEBUGFMT " this is function\n",DEBUGARGS);
return 0;
}
[email protected]:~/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;
}
[email protected]:~/cpropram/2# gcc -Wall funca.c funcb.c main.c
[email protected]:~/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
[email protected]:~/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
C语言常用宏定义
01: 防止一个头文件被重复包含
#ifndef COMDEF_H
#define COMDEF_H
//头文件内容
#endif
02: 重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。
typedef unsigned char boolean; /* Boolean value type. */
typedef unsigned long int uint32; /* Unsigned 32 bit value */
typedef unsigned short uint16; /* Unsigned 16 bit value */
typedef unsigned char uint8; /* Unsigned 8 bit value */
typedef signed long int int32; /* Signed 32 bit value */
typedef signed short int16; /* Signed 16 bit value */
typedef signed char int8; /* Signed 8 bit value */
//下面的不建议使用
typedef unsigned char byte; /* Unsigned 8 bit value type. */
typedef unsigned short word; /* Unsinged 16 bit value type. */
typedef unsigned long dword; /* Unsigned 32 bit value type. */
typedef unsigned char uint1; /* Unsigned 8 bit value type. */
typedef unsigned short uint2; /* Unsigned 16 bit value type. */
typedef unsigned long uint4; /* Unsigned 32 bit value type. */
typedef signed char int1; /* Signed 8 bit value type. */
typedef signed short int2; /* Signed 16 bit value type. */
typedef long int int4; /* Signed 32 bit value type. */
typedef signed long sint31; /* Signed 32 bit value */
typedef signed short sint15; /* Signed 16 bit value */
typedef signed char sint7; /* Signed 8 bit value */
03: 得到指定地址上的一个字节或字
#define MEM_B(x) (*((byte *)(x)))
#define MEM_W(x) (*((word *)(x)))
04: 求最大值和最小值
#define MAX(x,y) (((x)>(y)) ? (x) : (y))
#define MIN(x,y) (((x) < (y)) ? (x) : (y))
05: 得到一个field在结构体(struct)中的偏移量
#define FPOS(type,field) ((dword)&((type *)0)->field)
06: 得到一个结构体中field所占用的字节数
#define FSIZ(type,field) sizeof(((type *)0)->field)
07: 按照LSB格式把两个字节转化为一个Word
#define FLIPW(ray) ((((word)(ray)[0]) * 256) + (ray)[1])
08: 按照LSB格式把一个Word转化为两个字节
#define FLOPW(ray,val) (ray)[0] = ((val)/256); (ray)[1] = ((val) & 0xFF)
09: 得到一个变量的地址(word宽度)
#define B_PTR(var) ((byte *) (void *) &(var))
#define W_PTR(var) ((word *) (void *) &(var))
10: 得到一个字的高位和低位字节
#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
11: 返回一个比X大的最接近的8的倍数
#define RND8(x) ((((x) + 7)/8) * 8)
12: 将一个字母转换为大写
#define UPCASE(c) (((c)>=‘a‘ && (c) <= ‘z‘) ? ((c) - 0x20) : (c))
13: 判断字符是不是10进值的数字
#define DECCHK(c) ((c)>=‘0‘ && (c)<=‘9‘)
14: 判断字符是不是16进值的数字
#define HEXCHK(c) (((c) >= ‘0‘ && (c)<=‘9‘) ((c)>=‘A‘ && (c)<= ‘F‘) \
((c)>=‘a‘ && (c)<=‘f‘))
15: 防止溢出的一个方法
#define INC_SAT(val) (val=((val)+1>(val)) ? (val)+1 : (val))
16: 返回数组元素的个数
#define ARR_SIZE(a) (sizeof((a))/sizeof((a[0])))
17: 返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
#define MOD_BY_POWER_OF_TWO( val, mod_by ) ((dword)(val) & (dword)((mod_by)-1))
18: 对于IO空间映射在存储空间的结构,输入输出处理
#define inp(port) (*((volatile byte *)(port)))
#define inpw(port) (*((volatile word *)(port)))
#define inpdw(port) (*((volatile dword *)(port)))
#define outp(port,val) (*((volatile byte *)(port))=((byte)(val)))
#define outpw(port, val) (*((volatile word *)(port))=((word)(val)))
#define outpdw(port, val) (*((volatile dword *)(port))=((dword)(val)))
19: 使用一些宏跟踪调试
ANSI标准说明了五个预定义的宏名。它们是:
__LINE__
__FILE__
__DATE__
__TIME__
__STDC__
C++中还定义了 __cplusplus
如果编译器不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。
__LINE__ 及 __FILE__ 宏指示,#line指令可以改变它的值,简单的讲,编译时,它们包含程序的当前行数和文件名。
__DATE__ 宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。
__TIME__ 宏指令包含程序编译的时间。时间用字符串表示,其形式为: 分:秒
__STDC__ 宏指令的意义是编译时定义的。一般来讲,如果__STDC__已经定义,编译器将仅接受不包含任何非标准扩展的标准C/C++代码。如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。
__cplusplus 与标准c++一致的编译器把它定义为一个包含至少6为的数值。与标准c++不一致的编译器将使用具有5位或更少的数值。
可以定义宏,例如:
当定义了_DEBUG,输出数据信息和所在文件所在行
#ifdef _DEBUG
#define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
#else
#define DEBUGMSG(msg,date)
#endif
20: 宏定义防止错误使用小括号包含。
例如:
有问题的定义:#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp += nr;}
应该使用的定义: #difne DO(a,b) do{a+b;a++;}while(0)
例如:
if(addr)
DUMP_WRITE(addr,nr);
else
do_somethong_else();
宏展开以后变成这样:
if(addr)
{memcpy(bufp,addr,nr); bufp += nr;};
else
do_something_else();
gcc 在碰到else前面的“;”时就认为if语句已经结束,因而后面的else不在if语句中。而采用do{} while(0)的定义,在任何情况下都没有问题。而改为 #difne DO(a,b) do{a+b;a++;}while(0) 的定义则在任何情况下都不会出错。
原文地址:http://blog.csdn.net/bailyzheng/article/details/7536797