异常处理/__LINE__ 与 __FILE__ 宏在调试和异常处理中的高级使用

文章目录

  • 概述
  • 痛点分析
  • _LINE_ 代码所在行号
    • _LINE_ 直接转为字符串
    • _LINE_ 作为整型数据使用
    • _LINE_标记宏函数的调用位置
  • _FILE_ 代码所在文件名
    • 简单实验
    • 不期望 _FILE_ 宏代表全路径
  • assert 使用了 _FILE_ 和 _LINE_
  • 借助TLS技术
  • 小结

概述

_LINE_和_FILE_是C/C++中的预定义宏,分别用于获取当前代码所在的行号和文件名。本文详细讲述了它们的使用场景和使用方法,消除了一些陈旧错误的理解,重点实践了它们在软件调试、系统异常处理过程中举足轻重的作用。
编译器在预处理阶段将它们替换为相应的值,具体来说:_LINE_宏会被替换为当前代码所在的行号,表示该行号在源文件中的位置;_FILE_宏会被替换为当前代码所在的文件名,表示包含该代码的源文件的文件名。通过在日志记录或错误消息中使用_LINE_宏,可以动态地获取代码中出现问题的位置,并将其包含在日志或错误消息中,以帮助开发人员在调试和错误处理中排查问题。要再次强调的是,这些宏的值是在编译时确定的,因此它们提供的行号和文件名是编译时的信息,而不是运行时的信息。

转载请标明原文链接,
https://blog.csdn.net/quguanxin/category_6223029.html

痛点分析

#define STRINGIFY(x) #x          //it won't take effect, if you just do this
#define TOSTRING(x) STRINGIFY(x) //it works
//
std::string generateErrorMessage(std::string msg) {return msg + " in " + __FILE__ + " at line " + /*std::to_string(__LINE__)*/ TOSTRING(__LINE__);
}
//
int main() {std::string errorMsg = generateErrorMessage("Something went wrong.");std::cout << errorMsg.c_str() << std::endl;system("pause"); return 0;
}

执行结果,
在这里插入图片描述
如上,我们期望标记的是第13行代码位置,而运行结果却只能是 LINE 出现的第9行代码。FILE 存在相似的情况。

LINE 代码所在行号

早些年由于对_LINE_宏的不够了解,以为,既然它是编译时确定的,那么就只能死板的在待调试行上写包含_LINE_宏的代码,而不能封装或传递它。而实际上,配合宏函数的使用或者将其作为size_t 类型的函数实参,可以非常灵活的使用它。_LINE_宏可以直接转为字符串、可以作为int类型使用、也可以封装在宏函数内部(最终客户端调用宏函数,LINE 被转换为宏函数所在的行),接下来我们展开论证。

LINE 直接转为字符串

#include <iostream>
#include <string>#define STRINGIFY(x) #x          //it won't take effect, if you just do this
#define TOSTRING(x) STRINGIFY(x) //it works// 定义一个宏来生成带有错误消息、文件名和行号的字符串
#define ERROR_MSG(msg) ("Error: " + std::string(msg) + " in " + __FILE__ + " at line " + TOSTRING(__LINE__))int main() {std::string errorMsg = ERROR_MSG("Something went wrong.");std::cout << errorMsg << std::endl;return 0;
}

在这里插入图片描述
在这里插入图片描述
(操作符#)是C/C++中的预处理器操作符,称为字符串化操作符(stringify operator)。在预处理阶段,它可以将宏参数或标识符转换为字符串常量。具体来说,#操作符会在宏展开过程中将其后面的标识符或参数转换为一个以双引号括起来的字符串。

LINE 作为整型数据使用

我们以 ros2/rcutils/error_handling.h中的函数为例,

/// Set the error message, as well as the file and line on which it occurred.
/*** This is not meant to be used directly, but instead via the* RCUTILS_SET_ERROR_MSG(msg) macro.** The error_msg parameter is copied into the internal error storage and must* be null terminated.* The file parameter is copied into the internal error storage and must* be null terminated.** \param[in] error_string The error message to set.* \param[in] file The path to the file in which the error occurred.* \param[in] line_number The line number on which the error occurred.*/
RCUTILS_PUBLIC void rcutils_set_error_state(const char * error_string, const char * file, size_t line_number);/// Set the error message, as well as append the current file and line number.
/*** If an error message was previously set, and rcutils_reset_error() was not called* afterwards, and this library was built with RCUTILS_REPORT_ERROR_HANDLING_ERRORS* turned on, then the previously set error message will be printed to stderr.* Error state storage is thread local and so all error related functions are* also thread local.** \param[in] msg The error message to be set.*/
#define RCUTILS_SET_ERROR_MSG(msg) \do {rcutils_set_error_state(msg, __FILE__, __LINE__);} while (0)

如上,首先在编译预处理阶段,LINE 被替换为 RCUTILS_SET_ERROR_MSG 宏函数的代码行号,然后其作为一个整型数据,也即作为 rcutils_set_error_state 函数 line_number 参数的实参被传递。接下来我们定义一个简单的可接收 _FILE_, __LINE__实参的函数,

//形参类型分别对应 const char* 和 size_t 
std::string generateErrorMessage(std::string msg, const char* file, size_t line_no) {return msg + " in " + file + " at line " + std::to_string(line_no);
}
//
int main() {std::string errorMsg = generateErrorMessage("Something went wrong.", __FILE__, __LINE__);std::cout << errorMsg << std::endl;system("pause");  return 0;
}

在这里插入图片描述
执行结果如上,LINE 被识别为其出现位置所在的行号。这种方案很好理解,但在实际使用中每次都要去传递 FILELINE 数据,让人感觉不是很舒服。一种更高级的办法是,将上述 generateErrorMessage 用宏函数封装。具体我们看下一小节。

_LINE_标记宏函数的调用位置

一个应对策略就是,定义宏函数,

//
#define ERROR_MSG(errorMsg, msg) \
{ \errorMsg = generateErrorMessage("Error:" + std::string(msg), __FILE__, __LINE__); \
}  \
//一种更优雅的写法是
#define ERROR_MSG(errorMsg, msg) \
do { errorMsg = generateErrorMessage("Error:" + std::string(msg), __FILE__, __LINE__); } while (0) 

C/C++语法要求在宏展开时,宏展开的结果必须是一个完整的语句。故在使用宏定义时,通常使用do {…} while(0)的技巧可以确保宏的语法完整性,使其在被展开时能够像代码块一样使用,且可以避免语法错误,提高代码可读性。当然,你也可以仅使用花括号。
在这里插入图片描述
测试结果如下,
在这里插入图片描述
如上测试结果表明,ERROR_MSG函数中无论_LINE_出现在其中的第几行,都无关紧要,_LINE_标记的是 ERROR_MSG 宏的调用位置(如上图行号为27行),而不是 _LINE_标记的直接位置 (如上图行号22行)。至此,算是消除了对 _LINE_的一个大误会,它的使用方法,远比我之前以为的要灵活。

FILE 代码所在文件名

在前文讲述 LINE 宏的过程中,也同时完成了 FILE 宏的使用实践,它是一个字符串常量,表示当前源文件的文件名,包括文件的路径,其对应的数据类型是 const char* ,也即 C 语言字符串。

简单实验

#include <stdio.h>
#include <iostream>
//
int main() {printf("当前源文件名:%s\n", __FILE__);system("pause");  return 0;
}

在这里插入图片描述
如上,输出 FILE 所在的代码文件的全路径。当资源很紧张,或者文件路径较深的时候,全路径名就会很烦人,咋办?

不期望 FILE 宏代表全路径

如上一小节,在Windows上使用 _FILE_ 宏,默认情况下其代表的是源代码文件的全路径名称,但在大多情况下,这会显得有点冗余、浪费资源。一般情况在同一个项目下,存在同名文件的可能性不大,同名且内容相同的可能更是不存在,因此我们仅保留文件名就可以。为此我们对 _FILE_ 宏进行如下重定义,

#ifdef _WIN32
#define FILE_NAME (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#else
#define FILE_NAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif

定义一个名为FILE_NAME的宏,它使用strrchr函数来查找最后一个斜杠字符’/',并返回该字符后面的字符串部分。如果没有斜杠字符,则直接返回__FILE__宏的值。要注意的是,在不同操作系统上,文件路径使用不同的分隔符。对比效果如下,清澈了许多,
在这里插入图片描述
测试用的源代码如下,

#include <stdio.h>
#include <iostream>
//
#ifdef _WIN32
#define FILE_NAME (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#else
#define FILE_NAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
//
int main() {printf("当前源文件名:%s\n", FILE_NAME);system("pause"); return 0;
}

assert 使用了 FILELINE

int main() {printf("当前源文件名:%s, 当前代码行号:%s\n", FILE_NAME, __LINE__);assert(0 == 1);system("pause"); return 0;
}

在这里插入图片描述
通过上述测试,可以猜测,assert 内部极有可能是封装了 FILELINE 预处理宏的。看一下源码,

#ifdef NDEBUG#define assert(expression) ((void)0)
#else_ACRTIMP void __cdecl _wassert(_In_z_ wchar_t const* _Message,_In_z_ wchar_t const* _File,_In_   unsigned       _Line);#define assert(expression) (void)(                                                       \(!!(expression)) ||                                                              \(_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), (unsigned)(__LINE__)), 0) \)
#endif

与我们前面自定义函数的使用方法一致,先以 char* 和 int 为参数类型,定义函数,然后,使用宏封装此函数,这样,LINEFILE 就可以用来代表宏函数(assert) 的调用位置。

借助TLS技术

参见 src/error_handling.c 中的实现,定义线程本地存储TLS变量,

// g_ is to global variable, as gtls_ is to global thread-local storage variable
RCUTILS_THREAD_LOCAL bool gtls_rcutils_thread_local_initialized = false;
RCUTILS_THREAD_LOCAL rcutils_error_state_t gtls_rcutils_error_state;
RCUTILS_THREAD_LOCAL bool gtls_rcutils_error_string_is_formatted = false;
RCUTILS_THREAD_LOCAL rcutils_error_string_t gtls_rcutils_error_string;
RCUTILS_THREAD_LOCAL bool gtls_rcutils_error_is_set = false;

到这里就有点偏了… 已经超出了这个小主题… 跑到错误处理中了…

小结

在《异常处理/分析ROS2异常处理的设计和实现思路》(尚未发布)一文中,有提到过,针对调试信息,越直接越好,而 LINEFILE 宏所表现出来的,几乎就是最直接的。
站在开发者的角度上,无论是何种形式的异常处理,都是手段,我们根本目的始终是快速定位程序运行过程中的问题,并尽力地使其从问题中恢复正常运行。不同于此的,用户角度,作为软件的使用者,用户希望看到的告警信息应该是,可读性强、及时性好、清晰明了、具体详细的,并且好的告警信息不仅指出问题,还应该提供解决方案或建议,是可以操作和控制的。用户绝对不希望看到晦涩难懂的告警信息,而是希望能够快速地理解问题所在,因此给用户的告警信息必须是能简练和准确描述问题本质和原因的。虽说是具体详细,但也绝不是详细到哪个代码行,这是很容易理解的。用户期望了解的问题的具体细节,应该是业务层级的,操作层级的,而不是系统实现层级。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/10024.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS开发案例:【生活健康app之编写通用工具类】(5)

本节将介绍日志打印、时间换算等通用工具类的编写和使用&#xff0c;工具类可以简化应用代码编写和业务流程处理。 日志类 日志类Logger旨在提供一个全局的日志打印、日志管理的地方&#xff0c;既可以规范整个应用的日志打印&#xff0c;也方便日后对日志工具类进行修改&…

利用106短信群发平台能否提升沟通效率?

利用106短信群发平台确实能够显著提升沟通效率&#xff0c;具体体现在以下几个方面&#xff1a; 1.快速传递信息&#xff1a;106短信群发平台能够实现信息的快速传递。一旦设置好发送内容和接收群体&#xff0c;短信便能在瞬间发送至大量用户。这种即时性确保了信息的迅速传达…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-15.4讲 GPIO中断实验-IRQ中断服务函数详解

前言&#xff1a; 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM&#xff08;MX6U&#xff09;裸机篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

java对象互换工具类

1:将Object类型转成json字符串 /*** 将对象转为字符串* param obj* return*/public static String toString(Object obj) {if(obj null) {return null;}if ("".equals(obj.toString())) {return null;}if (obj instanceof String) {return obj.toString();}try {Ob…

Spring Cloud Gateway 11种断言工厂

系列文章目录 文章目录 系列文章目录前言前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 Spring Cloud Gateway路由匹配是Spring WebFlux基础功能的一部分,在Spri…

你认识edge吗,edge是做什么的

简介 Microsoft Edge&#xff08;研发代号为Project Spartan&#xff0c;又译作微软边缘浏览器&#xff0c;Edge浏览器&#xff09;是一个由微软研发的基于Chromium开源项目及其他开源软件的网页浏览器&#xff0c;于2015年1月21日公布&#xff0c;2015年3月30日公开发布第一个…

pyqt曲线轨迹运动

pyqt曲线轨迹运动 pyqt QPropertyAnimation介绍曲线轨迹运动代码 pyqt QPropertyAnimation介绍 QPropertyAnimation 是 PyQt中的一个类&#xff0c;它用于对 Qt 对象的属性进行动画处理。通过使用 QPropertyAnimation&#xff0c;你可以平滑地改变一个对象的属性值&#xff0c…

闭散列哈希表

一、什么是 哈希 &#xff1f; 1.1 哈希概念 与 哈希冲突 在正式介绍闭散列哈希之前&#xff0c;我们需要明确 哈希 的概念。 哈希 &#xff1a;构造一种数据存储结构&#xff0c;通过函数 HashFunc() &#xff0c;使 元素的存储位置 与 其对应的键值 建立一一映射关系&…

国产开源物联网操作系统

软件介绍 RT-Thread是一个开源、中立、社区化发展的物联网操作系统&#xff0c;采用C语言编写&#xff0c;具有易移植的特性。该项目提供完整版和Nano版以满足不同设备的资源需求。 功能特点 1.内核层 RT-Thread内核包括多线程调度、信号量、邮箱、消息队列、内存管理、定时器…

JVM基础之垃圾回收

垃圾回收 1. Base 内存泄漏&#xff1a;不再使用的对象在系统中未被回收 内存溢出&#xff1a;内存泄漏的积累 手动触发垃圾回收&#xff1a;System.gc(),该方法不一定会立即回收垃圾&#xff0c;仅仅是向JVM发送一个垃圾回收请求&#xff0c;具体是否需要垃圾回收由JVM自行…

怎么找回回收站里删除的XLS文件?5个恢复方法

我们经常会使用到XLS文件来存储和整理数据。然而有时候由于误操作或不小心&#xff0c;我们可能会将重要的XLS文件删除&#xff0c;并且这些文件可能还被清空出了回收站。面对这种情况许多人会感到焦虑和无助。但是不必过于担心&#xff0c;因为有专门的软件可以帮助我们找回这…

(Java)心得:LeetCode——5.最长回文子串

一、原题 给你一个字符串 s&#xff0c;找到 s 中最长的回文子串。 如果字符串的反序与原始字符串相同&#xff0c;则该字符串称为回文字符串。 示例 1&#xff1a; 输入&#xff1a;s "babad" 输出&#xff1a;"bab" 解释&#xff1a;"aba"…

【Linux】什么是进程?

一个正在执行的程序&#xff0c;我们称之为进程。 然后我们来顺着一条线来思考。 操作系统底层是用C语言编写的&#xff0c;而我们的进程&#xff0c;它会有各种属性&#xff0c;那么各种属性就可以用一个结构体来对进程的各个属性进行描述&#xff0c;然后这个结构体里面&…

手写一个SPI FLASH 读写擦除控制器(未完)

文章目录 flash读写数据的特点1. 扇擦除SE&#xff08;Sector Erase&#xff09;1.1 flash_se 模块设计1.1.1 信号连接示意图&#xff1a;1.1.2 SE状态机1.1.3 波形图设计&#xff1a;1.1.4 代码 2. 页写PP(Page Program)2.1 flash_pp模块设计2.1.1 信号连接示意图&#xff1a;…

Soviet Kitchen

苏联厨房-具有道具和带有碰撞器的模块化建筑部件的游戏环境资产 内部资产包: 网格-253 前言-98 材料-26 纹理-116 网格格式-(FBX) 纹理格式-(PNG) 资产列表: _BigShelf 多边形计数-1986 文本大小-2048x2048 可以 多边形计数-277 结构尺寸-512x512 _Celling 多边形计数-1 …

Codeforces Round 217 (Div. 2) A. Rook, Bishop and King(BFS)

Rook, Bishop and King 题面翻译 【题目描述】 佩蒂亚正在学习国际象棋。他已经学会如何移动王、车和象。让我们提示你如何移动国象棋子。棋盘有 64 64 64个棋格&#xff0c;呈 8 8 8\times8 88正方形。一个格子可以用 ( r , c ) (r,c) (r,c)来表示—— r r r指行&#xff…

Stable Diffusion的技术原理

一、Stable Diffusion的技术原理 Stable Diffusion是一种基于Latent Diffusion Models&#xff08;LDMs&#xff09;实现的文本到图像&#xff08;text-to-image&#xff09;生成模型。其工作原理主要基于扩散过程&#xff0c;通过模拟数据的随机演化行为&#xff0c;实现数据…

回表的原理竟然这么简单

“回表” 是指在使用辅助索引&#xff08;非聚簇索引&#xff09;作为条件进行查询时&#xff0c;由于辅助索引中只存储了索引字段的值和对应的主键&#xff08;聚簇索引&#xff09;键值&#xff0c;因此需要根据主键&#xff08;聚簇索引&#xff09;中的键值去查找实际的数据…

verilog中含有无关项的序列检测

编写一个序列检测模块&#xff0c;检测输入信号a是否满足011XXX110序列&#xff08;长度为9位数据&#xff0c;前三位是011&#xff0c;后三位是110&#xff0c;中间三位不做要求&#xff09;&#xff0c;当信号满足该序列&#xff0c;给出指示信号match。 程序的接口信号图如…

Python 中的 Lambda 函数:简单、快速、高效

大家好&#xff0c;今天再给大家介绍一个python的一个强大工具Lambda 函数&#xff0c;它允许你快速定义简单的匿名函数。这种函数是“匿名的”&#xff0c;因为它们不需要像常规函数那样被明确命名。 在本文中&#xff0c;我们将通过清晰的解释和实用的示例&#xff0c;深入了…