跟我学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;
}

六、总结

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

相关推荐
C++ 老炮儿的技术栈4 小时前
我们在设计tcp协议时,要传一个字符串过去,报文:头十长度十内容,是否要把‘\0‘也填入,长度是否包含‘\0‘
开发语言·数据结构·c++·mfc·c
传奇开心果编程4 小时前
【Rust入门练中学】第2课:变量、可变性与常量
开发语言·学习·rust
zr想努力4 小时前
从0开始配置Perforce4环境
开发语言
幸运小圣6 小时前
PointerEvent 常用属性与事件整理【JavaScript】
开发语言·javascript·ecmascript
长谷深风1116 小时前
评测AI Agent:三种裁判各司其职
java·大数据·开发语言·人工智能·ai agent
边境悍匪6 小时前
蜗牛学苑 Java 智能体学习 Day42|项目周开发技术汇总 1 思维导图复盘
java·开发语言·spring boot·学习·spring
君顾17 小时前
本地托管机构管理源码实战:需求拆解、数据建模与多端部署
java·开发语言·托管
传奇开心果编程8 小时前
【Rust入门练中学】 第3课:数据类型
开发语言·学习·rust
泡泡鱼(敲代码中)9 小时前
MySQL 学习笔记:DCL、函数与约束 —— 安全、效率、完整性的三板斧
开发语言·笔记·sql·学习·mysql·gitee
I_belong_to_jesus9 小时前
std::unique_ptr成员函数用法
c++