c语言编译时检查逻辑错误吗,C语言陷阱与技巧20节,自定义“编译时”assert方法,在代码编译阶段检查“逻辑”错误...

在C语言程序开发中,程序员写代码时应该考虑的“面面俱到”,这样才能写出功能稳定的程序。例如,在实现 open() 函数时,先完成它的功能固然是重要的,但是程序员还需要考虑各种“意外”,比如下面这种情况。

4c30524dc1fa4e78f655fd37415127b4.png

假设不存在 /dev/sth 这个文件,仍然调用 open() 函数打开它:

int fd = open("/dev/sth", O_RDONLY);此时 open() 函数不应该感到迷惑,而是具备处理这种“意外”的能力。标准库的 open() 函数在遇到这种情况时,会返回一个错误码,对应着“文件不存在”的错误信息。

所以我们在开发C语言程序的过程中,写出的代码也应具备这种处理“意外”的能力。处理“意外”最常用的方式之一就是返回一个错误码,输出一段错误提示信息,这一点其实之前的文章讨论过。

使用 assert

在C语言程序开发阶段,为了方便,我们可以在可能出现不预期的“意外”处使用 assert()。assert() 的C语言原型如下:

#include void assert(scalar expression);

27325f2342d46cc67c3f336634c9b44d.png

使用它需要包含 assert.h,assert() 接收一个参数 expression,可以是一个表达式,如果 expression 为真,则什么都不会发生。如果 expression 为假,则 assert() 会终止C语言程序,并且输出 assert 失败的代码位置。

例如下面这段C语言代码:

int fd = open("/dev/sth", O_RDONLY);assert(fd > 0);printf("fd = %d\n", fd);

7399146120038643cab8729921b08dfd.png

编译并执行,得到如下结果:

# gcc t.c# ./a.out a.out: t.c:11: main: Assertion `fd > 0' failed.Aborted可以看出,第 12 行的 printf() 函数并没有被执行。这是因为程序运行环境里并没有 “/dev/sth” 这个文件,所以 open() 函数执行失败,传递给 assert() 的参数为假,C语言程序被终止,并且输出 t.c 源文件第 11 行代码 assert 失败。

assert() 可以输出出错的代码位置,这个特性在较为大型的C语言程序开发中是非常好用的,因为无需程序员再去手工调试代码,排查出错代码的位置了。

不过,assert() 在遇到假参数时,直接将C语言程序终止太过于死板。比如某个C语言程序有两套逻辑,第一套逻辑在 open() 函数成功打开文件时运行,第二套逻辑则在 open() 函数打开文件失败时运行。要是使用 assert() 判断 open() 函数是否成功打开文件,则第二套逻辑永远没有机会运行。

dbfc82ce56f543c6fdc2979a1898b318.png

所以,assert() 一般仅用于开发阶段帮助程序员定位错误,不能依赖 assert() 处理“意外”。事实上,为了便于使用,在定义了 NDEBUG 宏之后,assert() 就不再生成代码了,此时 assert() 相当于一个空格。请看下面这段C语言代码:

#include #include #include #include #define NDEBUG#include int main(){int fd = open("/dev/sth", O_RDONLY); assert(fd > 0);printf("fd = %d\n", fd);return0; }

2b7821d00ecf943e8229b064ce91dd62.png

编译上述C语言代码并执行,得到如下输出:

# gcc t.c# ./a.out fd = -1编译时 assert

可以看出,assert() 用于处理C语言程序可能出现诸多预期之外的“意外”时很有用,它能够自己输出究竟哪一个“意外”发生。但是 assert() 也是死板的,它在遇到假条件时直接把程序终止,剩余的代码逻辑不再有机会执行。

另外还有一点要说明,assert() 本身也会影响C语言程序的运行效率,这也是它常常只被使用在开发阶段的另一个原因。

5811242cb6b729f84457e2d327542b3d.png

其实仔细想想,使用 assert() 的目的其实只是希望它能够在C语言程序遇到不预期的“意外”时提醒程序员,我们并不关心 assert() 是否参与程序运行。如果使用 assert() 判断的是常量表达式,那我们可以自己定义一个 static_assert() 宏,并且让它在编译时就判断条件表达式是否成立,这样的宏可能在某些场合更加好用。

那该如何实现编译时 assert 这个功能呢?

其实很简单,首先应该明白数组的长度不可能是负数,基于这一点,static_assert() 宏就容易实现了,请看下面的C语言代码:

#define static_assert(expr) \do{ char tmp[(expr)?1:-1]; }while(0)如果条件表达式为真,则 static_assert() 宏会定义一个长度为 1 的数组,否则就会尝试定一个长度为 -1 的数组,此时必定无法编译通过。这里值得一提的一个小技巧是使用 {} 符号将定义的 tmp 数组的作用域限定在本次调用的 static_assert 宏里,避免多次调用 static_assert 时出现重复定义。

写出如下C语言代码测试之:

int main() { static_assert(2>1);printf("assert 2>1\n");static_assert(2<1);printf("assert 2<1\n");return0; }

ef079cbcf565efea27520162e1780ce4.png

编译这段C语言代码,得到如下输出:

093896b7cbd6316fb0fe59b01892297b.png

显然,static_assert() 宏在编译阶段就将假条件表达式找出来了。可能有些读者会觉得如果 assert 成功,就会定义一个 tmp 数组,虽然它的长度很短,但是仍然浪费了栈空间。其实这里可以把长度为零的数组,即:

#define static_assert(expr) \do{ char tmp[(expr)?0:-1]; }while(0)在 assert 成功时会执行 char tmp[0];,它的长度为 0,感兴趣的读者可以使用 sizeof() 测试一下。到这里,我们就较为粗略的定义好了 static_assert 宏,它在编译阶段就能发现假条件。

小结

本节主要介绍了 assert() 的使用,应该能够发现,在开发阶段,它能够帮助程序员快速的定位“意外”,也讨论了 assert() 的不足之处,并在此基础上自己定义了“编译时”的static_assert 宏。按照这样的思路,其实还有很多定义 static_assert() 宏的其他方法,具体哪些方法留给读者自己思考了。

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

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

相关文章

springboot 多个sevice类实现同一接口的调用

参考页面&#xff1a;http://blog.csdn.net/xiao190128/article/details/54890759/&#xff1b;感谢分享 service 是有用的相当于 xml配置中得bean id service 也可以不指定 不指定相当于 bean id com. service.service 就是这个类的全限定名,表示给当前类命名一个别名&a…

影场与属性访问器界面

卡尔迪亚&#xff08;Carl Dea&#xff09;最近跟踪了我的一篇名为“ 保存内存”的博客文章&#xff01; 为属性使用阴影字段 。 在他的博客中&#xff0c;他建议使用一个称为“属性访问器”的接口来消除使用阴影字段所需的大量样板代码。 卡尔还提到他尚未用大量数据测试他的方…

[ZJOI2012]数列

超级水的题还wa了一次 首先很容易发现其实就只有两个值并存 然后 要注意把数组初始化啊。。。可能后面有多余的元素&#xff08;对拍的时候由于从小到大就没跑出错&#xff09; #include <bits/stdc.h> using namespace std; int a[170],b[170],a1[170],a2[170],x1[170],…

在c语言中,可以使用动态内存分配技术定义元素个数可变的数组,C语言复制在线考题1精选.doc...

C语言复制在线考题1精选窗体顶端《C语言程序设计208304》综合测试返回测验列表大项 1 / 2 - 单项选择题60.0/ 70.0 分本大题共35道&#xff0c;每题2分&#xff0c;共计70分&#xff0c;答错、不答均不得分。题目 1 / 552.0/ 2.0 分有字符数组 a[80]和 b[80]&#xff0c;则正确…

信管家源代码c语言,用队列实现按层次创建二叉树的源代码,最好是C语言

满意答案Dcool2016.08.27采纳率&#xff1a;58% 等级&#xff1a;9已帮助&#xff1a;416人队列&#xff1f;&#xff1f;你每输入一个节点将其存入队列中&#xff0c;再输入它的左孩子&#xff0c;它的左孩子也会入队&#xff0c;我们取的时候应先取该节点的左孩子&#xf…

jboss4 java_JBoss核心Java Web服务

jboss4 java这篇博客文章涉及Web服务。 好吧&#xff0c;更确切地说&#xff0c;它处理JBoss上的“普通” java Web服务。 这意味着我们将创建一个没有任何其他框架&#xff08;例如CXF&#xff0c;Axis等&#xff09;的Web服务。 JBoss它自己提供对Web服务的支持。 因此&#…

Java中的注解是如何工作的?

自Java5.0版本引入注解之后&#xff0c;它就成为了Java平台中非常重要的一部分。开发过程中&#xff0c;我们也时常在应用代码中会看到诸如Override&#xff0c;Deprecated这样的注解。这篇文章中&#xff0c;我将向大家讲述到底什么是注解&#xff0c;为什么要引入注解&#x…

android 广告弹出层,安卓广告活动弹窗控件 android-adDialog

软件介绍android-adDialog&#xff0c;一个简单、强大的广告活动弹窗控件。显示一个默认广告弹窗&#xff0c;支持单广告活动、多广告活动&#xff0c;当弹窗显示多广告是默认显示底部小圆圈&#xff0c;当显示单活动时默认不显示底部小圆圈&#xff1b;默认支持弹窗从上&#…

面试技巧

本人是一名95后的老奶奶了&#xff0c;是一名彻头彻尾的程序媛&#xff0c;这是本人的第一篇博客&#xff0c;只是随笔&#xff0c;写写自己的心得而已&#xff0c;写的不好勿喷&#xff01;&#xff01;&#xff01; 学习计算机大概已经四年了&#xff0c;在这期间接触过各种计…

(企业 / 公司项目)如何使用分布式任务调度框架Quartz集成 和 SpringBoot自带的定时任务集成?

SpringBoot自带的定时任务 首先在你的微服务项目中创建一个新的模块&#xff0c;定时调度模块 pom.xml里面关联公共模块common的依赖其他不需要改变 然后启动类别删&#xff0c;启动项目是否报错&#xff0c;写一个简单的测试类访问路径是否成功 package com.jiawa.train.bat…

在Spring中配置多个View解析器

1.简介 在Spring中&#xff0c;提供了View Resolver来使用模型中可用的数据来解析视图&#xff0c;而无需与JSP&#xff0c;Velocity或Thymeleaf等View技术紧密绑定。 Spring可以根据需要轻松灵活地配置一个或多个View Resolver 。 2. Spring MVC应用程序流程 在继续理解多个V…

android 知识体系

转载于:https://www.cnblogs.com/mamamia/p/8567570.html

android webview webp,iOS WebView中使用webp格式图片的方法

webp格式图片webp格式图片是google推出的&#xff0c;相比jpg png有着巨大的优势&#xff0c;同样质量的图片webp格式的图片占用空间更小&#xff0c;在像电商这样图片比较多的App中&#xff0c;使用webp格式图片会很有优势。引言很早之前&#xff0c;我们的项目中就已经采用了…

呵呵!Function构造函数

今天准备吐槽一下Function构造函数。 我们知道&#xff0c;不管是函数声明还是函数表达式都是基于词法作用域的&#xff0c;明白这点在闭包中十分重要&#xff0c;譬如&#xff1a; var aglobal; function foo(){var alocal;return function(){console.log(a);} } foo()(); …

android关机位置定位,Android5.0关机充电动画位置

充电动画位置bootable\bootloader\lk\dev\logo充电动画对应的分辨率&#xff1a;\bootable\bootloader\lk\project\XX.mkBOOT_LOGO : wxga#LOCAL_DIR : $(GET_LOCAL_DIR)TARGET : teft6752_lwt_lMODULES app/mt_boot \dev/lcmMTK_EMMC_SUPPORT yesDEFINES MTK_NEW_COMBO_EMM…

nosql的数据服务_使用NoSQL实现实体服务–第3部分:CouchDB

nosql的数据服务在本系列的第2部分中 &#xff0c;我使用SOA的“合同优先”技术创建和部署了产品实体服务&#xff0c;现在&#xff0c;我将致力于服务实现的NoSQL数据库方面。 正如我在第1部分中已经提到的那样&#xff0c;我已经选择CouchDB作为我的NoSQL数据库&#xff0c;选…

缓存穿透、缓存击穿与失效时的雪崩效应

问题&#xff0c;原理&#xff0c;解决方案转载于:https://www.cnblogs.com/tuhooo/p/8573614.html

android电视root权限获取,电视盒子/ 智能电视如何通过ADB获取ROOT权限?

如何通过adb获取root权限(安卓电视盒和智能电视通用)?Android 系统rom里面最主要的就3个文件&#xff1a;boot.img、system.img、userdata.img其中boot.img 存放着内核以及Android系统的配置信息&#xff0c;比如android系统各文件夹的读写权限&#xff0c;adb 的权限。所以如…

确定活动的热点垃圾收集器

StackOverflow问题查找正在运行哪种类型的垃圾收集 器&#xff0c;jvm的默认垃圾收集器 &#xff0c; 如何通过查看gc日志来查看正在运行的垃圾收集器&#xff1f; &#xff0c;以及如何知道HotSpot jvm的当前GC策略&#xff1f; 和博客文章如何以编程方式获取GC信息表明了人们…

spring3: Aspectj后置返回通知

Aspectj后置返回通知 接口&#xff1a; package chapter1.server;public interface IHelloService2 {public int sayAfterReturning(String param); }接口实现 package chapter1.service.impl;import chapter1.server.IHelloService2;public class HelloService2 implements IH…