• 使用__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  " ,         __FILE__, __LINE__);
          return   0 ;
    }


    输出结果:

    This fake error is in c: emp est.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: emp est.cpp:5"。而新的error函数则变成:

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

     

     

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

    #define  AT __FILE__ ":" __LINE__ 


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

    "c: emp est.cpp" ":" 5

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

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

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

    #define symbol(X) #X 
     


    因此,我把代码改为:

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


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

    c: emp est.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  " , location, msg);
    }
    int  main( int  ,  char ** )
    {
      error(AT,  " fake error " );
       return   0 ;
    }


    输出结果:

    Error at c: emp est est.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__被处理成字符串,给我们带来了不少方便。
  • 相关阅读:
    K近邻 Python实现 机器学习实战(Machine Learning in Action)
    sklearn-SVC实现与类参数
    从核函数到SVM原理--sklearn-SVM实现
    基于scikit-learn包实现机器学习之KNN(K近邻)-完整示例
    java集合框架
    面向对象第一周心得体会
    java面试总结
    Aaa
    测试
    在Java中执行Tomcat中startup.bat
  • 原文地址:https://www.cnblogs.com/jkred369/p/5011818.html
Copyright © 2020-2023  润新知