静态断言之一 assert 以及 通过宏定义处理文件包含关系

Posted 林兮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了静态断言之一 assert 以及 通过宏定义处理文件包含关系相关的知识,希望对你有一定的参考价值。

 

一、断言:运行时与预处理时

断言(assertion)是一种编程常用的手段。想必大家都见过 assert 吧。今天我们就来了解一下它。

通常情况下,断言就是将一个返回值总是需要为真的判别式放在语句中,用于排除在设计的逻辑上不应该产生的情况。

比如一个函数总需要输入在一定的范围内的参数,那么程序员就额可以对该参数使用断言,以迫使在该参数发生异常的时候程序退出,从而避免程序陷入逻辑的混乱。

 

从一些意义上讲,断言并不是正常程序所必须的,不过对于程序调试来说,通常断言能够帮助程序开发者快速定位那些违反了某些前提条件的程序错误。

例子:

#include<cassert>
using namespace std;

char* ArrayAlloc(int n)
{
    assert(n > 0);
    return new char[n];
}

int main()
{
    char* a = ArrayAlloc(0);
}

结果显示,很明显程序崩溃了。

 

这一点上,虽然异常可以防止程序崩溃,使程序得以继续执行,但是该结束程序的时候,还是要果断地用assert 结束程序

我们应该合理应用断言和异常。

 

在C++中,程序员也可以定义宏NDEBUG来禁用assert 宏。这对发布程序来说还是必要的。

因为程序用户退出总是敏感的,而且部分的程序错误也未必会导致程序全部功能失效。(比如上一段提到的异常就可以避免这种情况。)

那么通过定义NDEBUG宏发布程序也可以尽量避免程序退出的状况。而当程序有问题时,通过没有定义宏NDEBUG的版本,程序员则可以比较容易地找到出问题的位置。

 

事实上,assert 宏在<cassert>中的实现方式类似如下形式:

#ifdef NDEBUG
# define assert(expression) (static_cast<void>0)   //!!!!!
#else
  ...
#endif

可以看到,一旦定义了NDEBUG宏,assert 宏将被展开为一条无意义的C语句(通常被编译器优化掉)

但是有一个问题,定义NDEBUG宏的时候必须将其定义在相应头文件之前才行!!

 

在上一篇(三)中,我们还看到 #error 这样的预处理指令,而事实上,通过预处理指令#if 和#error 的配合,也可以让程序员在预处理阶段进行断言。

这样的用法也是极为常见的。

书上对于这方面是这样说的,原话如下:(由于没有相关的设备支持,所以后面自己写了个简单的工程测试了一下)

比如GNU的cmathcalls.h 头文件中(在我们的实验机上,该文件位于/user/include/bits/cmathcalls.h),我们会看到如下代码:
#ifndef _COMPLEX_h
#error "Never use <bits/cmathcalls.h> directly; include<complex.h>instead."
#endif
如果程序员直接包含头文件<bits/cmathcalls.h>并进行编译,就会引发错误。#error 指令会将后面的语句输出,从而提醒用户不要直接使用这个头文件,而应该包含头文件<complex.h>。

 

我们用这种方法测试一下:

我们首先写一个头文件  test.h 里面有一个类 _Test 文件定义宏为 _TEST

//test.h
#define _TEST

class _Test
{
    int m_int;
public:
    _Test():m_int(0){}
    const int get_int()const { return m_int; }
};

我们再写另一个头文件 test1.h 

//test1.h
#ifndef _TEST

  #error "请引用正确的头文件 test.h"

#endif

意思是,如果该文件没有重定义宏 _TEST ,那么就用原版本

主调函数测试:

#include<iostream>
#include"test1.h"
using namespace std;  
int main()
{
    _Test a;
    cout << a.get_int() << endl;
}

我们试图调用test1.h文件中的_TEST类,但是该文件没有重新定义该类

触发错误。

在次测试:

#include<iostream>
#include"test.h"
#include"test1.h"
using namespace std;  
int main()
{
    _Test a;
    cout << a.get_int() << endl;
}

 

这样一来,通过预处理时的断言,库发布者就可以避免一些头文件的引用问题。

 

感谢您的阅读,生活愉快~

以上是关于静态断言之一 assert 以及 通过宏定义处理文件包含关系的主要内容,如果未能解决你的问题,请参考以下文章

为啥在发布版本中断言宏的定义不能只是`#define assert(expression) 0`?

C 标准库系列之assert.h

libc6:断言宏定义中的逗号运算符

宏定义的黑魔法——assert原理详解

gtest断言

如何用宏做static_assert?