跟我学C++中级篇—static_assert和assert

一、断言

对于开发者而言,编译期和运行期是两个显著不同的概念。编译期可能主要是用来处理语法和语义的处理等,而运行时则处理真正的资源分配、交互和运算等。在早期的开发中,它们二者基本是分开的,虽然谈不上泾渭分明,但确实联系不是多大。

但大牛们的意思是要无缝覆盖管理软件生命的全周期,随着软件技术的发展。安全性的要求越来越高,特别是针对C++这类灵活性极高的语言,其灵活性的优势随着安全性的要求越来越高,反而在不断的降低。不然不会现在对Rust给出的评价那么高。

C++的特性就是灵活,就意味着可以吸收别的语言的先进经验。那么,如果在编译期把能处理的安全风险都处理掉,不就能够大幅提高安全性么?如果说运行时的检查是一种及时的止损,那么编译期检查就是一种预防机制。

而assertion,断言。就提供了对编译期和运行时的机制。

二、运行时断言

早期的语言都是从运行时的判断出发的。毕竟从现实世界的经验来看,事情如果不发生,就投入精力去处理,不符合人们的直观的认知。在计算机世界当然也是如此,C++语言中提供了assert宏(当然也有平台等扩展的类似的宏)。其实很容易理解,就是看代码中执行是否为真。如果不为真就报一个错误。它有点类似于家庭中用电中的保险丝,如果小于安全电流,就直接使用;否则就熔断,牺牲小我,成就安全。

assert宏就是运行时检查条件的一种"保险",防止错误进一步扩大,导致不可控的意外的风险。assert宏满足了开发者对动态运行时的一种实时的监控,并可以在出现问题后,将相关的问题快速定位并反馈给开发者。为解决问题提供最基础的支持。

其定义一般为:

c 复制代码
#ifdef NDEBUG
    #define assert(expression) ((void)0)  // 展开为空操作,不生成任何代码
#else
    #define assert(expression) \
        (void)( (!! (expression)) || \
        (_wassert(_CRT_WIDE(#expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
#endif

其应用一般如下:

c 复制代码
assert(expression)

assert宏出现很早,可以认为它是从C语言中继承过来的。其内部的实现,一般是当条件为假时,会调用系统的接口函数来退出当前程序,这意味着,这种问题是一种致命的问题。因此,对于可恢复的错误检查就不能使用它了。

三、编译期(静态)断言

安全是全生命周期的控制,所以编译期的断言控制也是不可避免的。没有就引入呗。以前标准不提供,还可以开发者自己写。但随着在C++11中提供了static_assert,就不需要再自己写了。静态断言可以认为是运行时断言的一种扩展。特别是C++中提供了模板编程和元编程,尤其是在新的标准中增加对编译期计算的支持,static_assert可谓是生逢其时。

其实可以理解为,能否把一些工作提前到编译期进行处理,哪怕只是一部分。毕竟编译期更类似于一种预防机制,可以不用在运行时耗费相关的资源就可以把问题,这当然是最优的选择。

static_assert的应用一般如下:

c 复制代码
static_assert(constant-expression, string-literal);

静态断言的出现,可以认为是一种断言全覆盖的必然结果。而且在未来的标准中,很可能会有更强大更安全的多种机制在编译期和运行时进行断言或类似机制的控制,比如前文提到的Concepts,但概念还是有一些复杂。所以后面肯定会有更简单的机制或标准出现。

四、分析

虽然说不管是静态断言还是运行时断言,其机制本质是相同的,目的也是一致的。但在应用时,还是有一些不同。典型的就是运行时的assert宏在Release版本下就不起作用了。这点非常重要,有不少的同学在实际的开发中掉进过这个坑里。

而对于static_assert来说,它的一个重点不是自己简单的做一个编译期的判断,重点是和其元编程的接口如std::is_integral、std::is_same等一起配合使用,达到特定的设计目的。特别是在和constexpr、constinit以及const if constexpr

另外,在模板编程中为了控制模板不同阶段的断言控制,还可以进行封装,当其进行实例化时再进行断言的判断。这些都是一些细节上的问题,需要开发者自行学习和掌握相关的技巧。

五、应用

下面看一下二者的基础应用的例子:

c 复制代码
#include <cassert>

// static_assert
static_assert(1 == 1, "OK");   // 这行能过
// static_assert(1 == 0, "ERR"); // 取消这行注释,编译立即报错

int main() {
    // assert
    assert(1 == 0); // 编译通过,但运行到这里异常
    //或者:C++26中已经将其完善为变参宏,不再需要下面的方式
    //assert((1== 0) && "err code!");//可类似static_assert输出相关的消息
    return 0;
}

六、总结

总体来说,安全不是一个阶段的约束。正如木桶原理,只要有一部分没有做足功课,都有可能引入异常和错误。正如医疗技术的进步,更倾向于预防而非治疗。扁鹊不是说最厉害的是大哥么。开发亦是如此!

相关推荐
geovindu7 分钟前
rust: Read-Write Lock Pattern
开发语言·后端·设计模式·rust·读写锁模式·同步型模式
怪我冷i8 分钟前
Rust智能指针Rc<T> 使用总结
开发语言·后端·rust
全栈陈序员14 分钟前
【Python】基础语法入门(三十三)—— 日志系统详解:用 logging 记录程序运行
java·开发语言·python
ESDWAN15 分钟前
SD-WAN 是什么?工作原理、适用场景与选型要点
开发语言·网络·数据库·架构·php
iCxhust37 分钟前
C#双时区时钟设计
开发语言·c#
小菜要扣篮38 分钟前
Ubuntu 下使用 vscode 打开 C++ 项目后,无法通过 F12 定位到定义问题解决
c++·vscode·ubuntu
wuminyu39 分钟前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
sbjdhjd1 小时前
从污点追踪断点到 PHP-FPM 请求队列:HIDS PHP WebShell 样本复盘
开发语言·网络安全·数据挖掘·渗透测试·开源·php·shell
旖旎夜光1 小时前
LCR 170:交易逆序对的总数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
C++ 老炮儿的技术栈1 小时前
Linux 动态库踩坑:明明重新编译了 .so,调用函数返回值却一直不变(libfjrobot.so 版本号异常排查实录)
linux·c语言·网络·c++·visual studio