0
点赞
收藏
分享

微信扫一扫

C++学习------cassert头文件的作用与源码学习

引言

cassert是对assert.h头文件的封装,里面定义了一个assert函数,可以用于异常判断,那么它的使用方式及实现原理是怎么样的呢?我们一起来学习一下。

cassert的学习

一个小例子

我们通过下面这个例子来学习cassert头文件的使用:

#include <iostream>
//#define NDEBUG
#include <cassert>void printNum(int* num){
assert(num != nullptr);
std::cout << "num:" << *num << std::endl;
}

int main(){
int a = 333;
int *b = nullptr, *c = nullptr;

b = &a;

printNum(b);
printNum(c);

return 0;
}

使用g++编译执行:​​g++ -g test.cpp -o test && ./test​​ 得到结果:

num:333
test: test.cpp:14: void printNum(int*): Assertion `num != nullptr' failed.
Aborted (core dumped)

可见,在出现异常的位置打印了对应的可执行文件名、源文件名、函数信息以及assert判断失败的原因,然后出现了主动aborted的报错。

第二次我们在​​#include <cassert>​​之前增加定义​​#define NDEBUG​​,再进行编译执行,得到如下结果:

num:333
Segmentation fault (core dumped)

# 出现段错误,使用gdb进行调试
gdb ./test

# 结果如下:
(gdb) r
Starting program: /home/sangyu/WorkSpace/Code/C++/CPPStudy/CHeaderFile/0_assert.h/test
num:333

Program received signal SIGSEGV, Segmentation fault.
0x00005555555549a0 in printNum (num=0x0) at test.cpp:15
15 std::cout << "num:" << *num << std::endl;
(gdb) bt
#0 0x00005555555549a0 in printNum (num=0x0) at test.cpp:15
#1 0x0000555555554a12 in main () at test.cpp:25
(gdb) display num
1: num = (int *) 0x0

这一次只是出现了段错误,并没有对应的报错信息打印,这是因为在​​#include <cassert>​​之前增加定义​​#define NDEBUG​​,可以屏蔽后续assert的使用。通过gdb调试结果,我们可以看到报错出现的对应行号,以及对应指针num为0,这样也能找到问题的根本原因。

cassert的源码实现

参考文件

​​www.aospxref.com/android-12.…​​

​​www.aospxref.com/android-12.…​​

43  #include <sys/cdefs.h>
44
45 #undef assert
46 #undef __assert_no_op
47
48 /** Internal implementation detail. Do not use. */
49 #define __assert_no_op __BIONIC_CAST(static_cast, void, 0)
50
51 #ifdef NDEBUG
52 # define assert(e) __assert_no_op
53 #else
54 # if defined(__cplusplus) || __STDC_VERSION__ >= 199901L
55 # define assert(e) ((e) ? __assert_no_op : __assert2(__FILE__, __LINE__, __PRETTY_FUNCTION__, #e))
56 # else
57 /**
58 * assert() aborts the program after logging an error message, if the
59 * expression evaluates to false.
60 *
61 * On Android, the error goes to both stderr and logcat.
62 */
63 # define assert(e) ((e) ? __assert_no_op : __assert(__FILE__, __LINE__, #e))
64 # endif
65 #endif
66
67 #if !defined(__cplusplus) && __STDC_VERSION__ >= 201112L
68 # undef static_assert
69 # define static_assert _Static_assert
70 #endif
71
72 __BEGIN_DECLS
73
74 /**
75 * __assert() is called by assert() on failure. Most users want assert()
76 * instead, but this can be useful for reporting other failures.
77 */
78 void __assert(const char* __file, int __line, const char* __msg) __noreturn;
79
80 /**
81 * __assert2() is called by assert() on failure. Most users want assert()
82 * instead, but this can be useful for reporting other failures.
83 */
84 void __assert2(const char* __file, int __line, const char* __function, const char* __msg) __noreturn;
85
86 __END_DECLS

可以看到,在include这个头文件之前,如果定义NDEBUG,那么assert宏将被定义为

​# define assert(e) __assert_no_op​​,追踪一下这个的实现

​#define __assert_no_op __BIONIC_CAST(static_cast, void, 0)​​继续追踪 ​​www.aospxref.com/android-12.…​​

57  #if defined(__cplusplus)
//定义了CPP,所以走这里,即static_cast<void>(0)其实是一句没有意义的语句,即什么都不做
58 #define __BIONIC_CAST(_k,_t,_v) (_k<_t>(_v))
59 #else
60 #define __BIONIC_CAST(_k,_t,_v) ((_t) (_v))
61 #endif

那么正常走的话又会是怎么样呢? 通过源码可以看到如果

​defined(__cplusplus) || __STDC_VERSION__ >= 199901L​​,那么就会将assert宏定义为

​# define assert(e) ((e) ? __assert_no_op : __assert2(__FILE__, __LINE__, __PRETTY_FUNCTION__, #e))​​即满足条件不做任何处理,不满足就调用​​__assert2​​函数

或者最后走到 ​​# define assert(e) ((e) ? __assert_no_op : __assert(__FILE__, __LINE__, #e))​​即调用​​__assert​​函数

__assert2与__assert函数

31  #include <assert.h>
32
33 #include <async_safe/log.h>
34
35 void __assert(const char* file, int line, const char* failed_expression) {
36 async_safe_fatal("%s:%d: assertion "%s" failed", file, line, failed_expression);
37 }
38
39 void __assert2(const char* file, int line, const char* function, const char* failed_expression) {
40 async_safe_fatal("%s:%d: %s: assertion "%s" failed", file, line, function, failed_expression);
41 }

通过源码来看,实际上这两个函数都是调用了函数​​async_safe_fatal()​​来打印报错信息:

  • __FILE__是当前的文件名
  • __LINE__是当前的报错函数
  • __PRETTY_FUNCTION__是当前的函数名,可以获取更为详细的函数信息
  • #e是将后面的参数进行字符串化操作,即"e" 这样看和我们之前看到的报错打印信息对应上:

test: test.cpp:14: void printNum(int*): Assertion `num != nullptr' failed.

最后我们看看​​async_safe_fatal​​的实现:

//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/async_safe/include/async_safe/log.h45  // Formats a message to the log (priority 'fatal'), then aborts.
46 // Implemented as a macro so that async_safe_fatal isn't on the stack when we crash:
47 // we appear to go straight from the caller to abort, saving an uninteresting stack
48 // frame.
49 #define async_safe_fatal(...) \
50 do { \
51 async_safe_fatal_no_abort(__VA_ARGS__); \
52 abort(); \
53 } while (0) \

//http://www.aospxref.com/android-12.0.0_r3/xref/bionic/libc/async_safe/async_safe_log.cpp
573 void async_safe_fatal_no_abort(const char* fmt, ...) {
574 va_list args;
575 va_start(args, fmt);
576 async_safe_fatal_va_list(nullptr, fmt, args);
577 va_end(args);
578 }

534 int async_safe_format_log_va_list(int priority, const char* tag, const char* format, va_list args) {
535 ErrnoRestorer errno_restorer;
536 char buffer[1024];
537 BufferOutputStream os(buffer, sizeof(buffer));
538 out_vformat(os, format, args);
539 return async_safe_write_log(priority, tag, buffer);//后续还有多个函数调用,与操作系统相关
540 }

调用​​async_safe_fatal_no_abort​​打印信息后主动调用了abort,后续函数调用写入标准输出的部分我们就不再进行跟踪了。

综上,我们分析了cassert的源码,搞明白了其用法及原理,你明白了吗?

举报

相关推荐

0 条评论