使用__FILE__和__LINE__定位错误

[前言:使用__FILE__和__LINE__来定位错误已经屡见不鲜,然而其中一些道理又有几个人仔细探究过。本文参考了Curtis Krauskopf的一篇名为Using __FILE__ and __LINE__ to Report Errors 的文章,希望达到解惑之效。]

问题:当运行时错误产生时,我怎样才能得到包含C++文件名和行号的字符串信息?
回答:在C++中的__FILE__预编译指示器包含了被编译的文件名,而__LINE__则包含了源代码的行号。__FILE__和__LINE__的前后都包含了两个下划线,让我们仔细看看__FILE__所包含的每个字符:

_ _ F I L E _ _

下面展示了在控制台程序中如果显示文件名和代码行号。

#include  < stdio.h >

int  main( int  ,  char ** )
{
     printf( " This fake error is in %s on line %d\n " ,         __FILE__, __LINE__);
      return   0 ;
}

输出结果:

This fake error is in c:\temp\test.cpp on line 5

让我们更上一层楼

我想通过一个通用函数error()来报告错误,以使当某个错误发生时我能设置断点以及隔离错误处理(例如,在屏幕上打印错误信息或者写入日志)。因此,函数的原型应该是这样的吧:

void  error( const   char   * file,  const  unsigned  long  line, const   char   * msg);

调用方式如下:

error(__FILE__, __LINE__,  " my error message " );

预处理魔法

这里有三个问题需要解决:

  1. __FILE__和__LINE__在每次调用error时作为参数传入。
  2. __FILE和__LINE__前后的下划线很容易被遗忘,从而导致编译错误。
  3. __LINE__是一个整数,这无疑增加了error函数的复杂度。我绝不想直接使用整型的__LINE__,而通常都是将转换为字符串打印到屏幕或写入日志文件。

__FILE__和__LINE__应该被自动处理,而非每次作为参数传递给error,这样会让error的使用者感觉更爽些,它的形式可能是下面这样:

error(AT,  " my error message " );

我希望上面的宏AT展开为:"c:\temp\test.cpp:5"。而新的error函数则变成:

void  error( const   char   * location,  const   char   * msg);

因为Borland C++ Builder编译器能够自动合并相邻的字符串,因此我把AT写成下面这样:

#define  AT __FILE__ ":" __LINE__

然而它却罢工了,因为__LINE__被扩展成了整数而非字符串,所以宏展开后变成:

"c:\temp\test.cpp" ":" 5

这是一个无效的字符串,因为末尾有一个未被双引号包含的整数。

怎么办?别着急,一个特殊的预编译指示器“#”能够帮我们将一个变量转换成字符串。所以重新定义宏:

#define AT __FILE__ ":" #__LINE__

嘿嘿,这样总行了吧。别高兴得太早,这样也是不行的。因为编译器会抱怨#是个无效字符。其实,问题是#预编译指示器只有这样使用才会被正确识别:

#define symbol(X) #X

 

因此,我把代码改为:

#define  STRINGIFY(x) #x 
#define  AT __FILE__ ":" STRINGIFY(__LINE__)

然而,奇怪的结果产生了,__LINE__居然被作为了输出的一部分:

c:\temp\test.cpp:__LINE__: fake error

解决方法是再用一个宏来包装STRINGIFY():

#define  STRINGIFY(x) #x 
#define  TOSTRING(x) STRINGIFY(x) 
#define  AT __FILE__ ":" TOSTRING(__LINE__)

OK,我们用下面的代码来试试:

#include  < stdio.h > 
#define  STRINGIFY(x) #x 
#define  TOSTRING(x) STRINGIFY(x) 
#define  AT __FILE__ ":" TOSTRING(__LINE__) 
void  error( const   char   * location,  const   char   * msg)
{
  printf( " Error at %s: %s\n " , location, msg);
}
int  main( int  ,  char ** )
{
  error(AT,  " fake error " );
   return   0 ;
}

输出结果:

Error at c:\temp\test\test.cpp:11: fake error

Visual Studio下的实践
在《Windows核心编程》中,Jeffrey Richter提供了下面的宏在编译期输出有用信息:

#define  chSTR2(x) #x 
#define  chSTR(x)  chSTR2(x) 
#define  chMSG(desc) message(__FILE__ "(" chSTR(__LINE__) "):" #desc)

message是一个预编译指令,上面宏的使用方法是:

 #pragma chMSG(Fix  this  later)

结论

    1. 预编译指示器__FILE__和__LINE__能够提供有用的信息。
    2. __LINE__的处理方式远比我们想象的要复杂。
    3. __FILE__被处理成字符串,给我们带来了不少方便。
时间: 2024-10-13 15:21:39

使用__FILE__和__LINE__定位错误的相关文章

转载--C语言中的__FILE__、__LINE__和__func__

作    者:taric_ma 链    接:C语言中的__FILE__.__LINE__和__func__ 原链接:C语言中的__FILE__.__LINE__和__func__ C语言中的__FILE__用以指示本行语句所在源文件的文件名,举例如下(test.c): #include <stdio.h> int main() { printf("%s\n",__FILE__); } 在gcc编译生成a.out,执行后输出结果为: test.c 在windows的vc6.

程序猿之--C语言细节15(预处理命令细节#error、运算符#和##、__FILE__、__LINE__)

主要内容:预处理命令细节#error.运算符#和##.__FILE__.__LINE__ #include <stdio.h> /* 包含这个头文件,并不是将其所有函数都链接进程序*/ /* ##运算符 */ #define MK_ID(n) i##n /* 表示将两个记号连接 */ int MK_ID(1), MK_ID(2),MK_ID(3); /* 预处理后变成int i1,i2,i3;*/ /* 定义多个type##_max函数,函数返回类型和参数类型用define决定 * 如GENE

定位错误位置

对于每一个编程人员来说,如果自己的程序报错,那么简单的浏览一下错误信息就能大致明白错误原因及错误位置,即使一时间无法确定,但是经过简单的分析也能很快得出结论,这对于个人来说是非常方便的,但是,在某些时候我们要修改的并不是自己的代码,这个时候如果要是从都到尾的去理解一遍他人的代码,这样的话就是有点浪费时间了,所以如何快速定位就是非常重要了. 首先来说,对于PHP编程有一个非常好用的函数可以帮助我们定位错误信息,避免给用户提供不友好的交互体验. 1.网站的问题排查主要看日志,因为线上的网站一般不会开

接口自动化,断言方法,深度定位错误

接口自动化,断言方法,深度定位错误. 代码如下: 1 #!/usr/bin/env python 2 # -*- coding: utf-8 -*- 3 # @Time : 2017-07-27 13:49 4 5 # 断言方法,比较两个list或dict的不同之处 6 7 a= {'b':[1,2,5,8],'c':3,'d':2,'f':[1,2,3],'g':[1,2,3,[2,'2',2]],'h':'5'} 8 b= {'b':[1,2,'3'],'c':2,'e':'4','f':[

【Python】Selenium元素定位错误之解决办法

当使用class定位元素时发现报错: 错误信息:selenium.common.exceptions.InvalidSelectorException: Message: Compound class names not permitted(复合类的名称不允许) 网上查询资料得知: className不允许使用复合类名做参数[原文] 真实环境中元素往往使用复合类名(即多个class用空格分隔),使用className定位时要注意了,className的参数只能是一个class. 例如图中显示的c

ndk-stack 定位错误(动态加载的so)

我们一般都是使用ndk-stack 定位libs下的so错误,动态加载sd卡上的so时不能 多次load了,否则会出现定位位置出错. 操作命令 (adb路径)adb logcat | (ndk-stack路径)ndk-stack -sym (项目jni路径)/obj/local/armeabi-v7a(或者armeabi) 例如我的是这样的 E:\android\eclipse-adt\sdk\platform-tools>adb logcat | E:\android\android-ndk-

开源项目 RethinkDB 关闭,创始人总结失败教训(市场定位错误)

当我们宣布RethinkDB关闭时,我答应写一个调查分析.我花了一些时间来整理所得的教训和经验,现在可以清楚地写出来. 在HN讨论贴中,人们提出了许多关于为什么RethinkDB失败的原因,从莫名的人性和聪明的MongoDB营销人员:到没有建立一个有经验的上市团队:再到缺乏支持超过64-bit float 的数字类型……我将这些意见集中到这里. 其中一些原因确实是真的,但它们是症状而不是原因.例如,说我们未能赚钱的,它并没有说明我们失败的原因. 事后看来,有两个重要的原因导致了我们的失败 —— 

eclipse 安装 maven 集成插件后,M2_REPO定位错误

1. 首先更改maven安装目录下conf目录下的settings.xml,里面有一项: <localRepository>E:/documents/.m2/repository</localRepository> 2.改eclipse的配置,prefrences->maven->user settings...到maven的conf目录下的settings.xml.

__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"i