【C语言】C语言编程进阶:异常处理与程序稳定性全攻略

1. 概述

异常处理又称异常错误处理,它提供了处理程序运行时出现任何意外或异常情况的方法。异常处理通常是防止未知错误的发生所采取的处理措施,对于某一类型的错误,异常处理应该提供相应的处理方法。例如,在设计程序时,如果可能会碰到除0错误或者数组访问越界错误,程序员应该在程序中设计相应的异常处理代码以便发生异常情况时,程序做出相应的处理。

C语言作为一种过程式的编程语言,在错误处理方面并不像一些现代的高级语言(如Java、C#或Python)那样具有内置的异常处理机制。然而,通过一系列编程习惯和技巧,C语言程序员同样可以编写出健壮、稳定的程序,优雅地处理可能出现的异常和错误情况。

文章目录

  • 1. 概述
  • 2. 怎么做异常处理
    • 2.1 明确返回值的意义
    • 2.2 使用错误码和错误消息
    • 2.3 使用断言(assert)
    • 2.4 异常退出时清理已申请的资源
    • 2.5 记录日志到文件中
    • 2.6 编写清晰的文档和注释
    • 2.7 使用静态分析工具
  • 3. 总结

2. 怎么做异常处理

2.1 明确返回值的意义

C语言函数通常通过返回值来反馈操作的成功或失败。因此,为函数设计合理的返回值至关重要。例如,如果函数可能失败,则最好返回一个整数值,其中0表示成功,使用非0值表示特定的错误代码。

int my_function(void)
{// ... 执行一些操作 ...if (/* 操作成功 */) {return 0; // 成功} else {return -1; // 失败}
}

调用此函数的代码应该检查返回值,并根据需要处理错误。除此之外,绝大多数C库函数和系统调用都支持通过返回值来判断执行是否成功,如果执行失败则通过返回值来告诉调用者发生了什么错误。所以调用现有函数(不管是自己写的还是别人写的还是系统函数)时,如果不能保证一定执行成功,都必须判断返回值,不要纠结多那几行代码。

以开源软件ffmpeg中的一个函数为例进行演示。可以看到,该函数中一共出现了6个ifreturn组合在一起使用的代码段,除了最后一个return 0,前面5个都是为了容错处理而写的,正是这些看似无用 的代码使得该函数真正做到了有错知错(能检查出异常)并勇于承认错误(能返回错误)。

static int configure_simple_filtergraph(FilterGraph *fg)                                                                                                                                                                                                                       
{OutputStream *ost = fg->outputs[0]->ost;AVFilterContext *in_filter, *out_filter;int ret;avfilter_graph_free(&fg->graph);fg->graph = avfilter_graph_alloc();if (!fg->graph)return AVERROR(ENOMEM);switch (ost->st->codec->codec_type) {case AVMEDIA_TYPE_VIDEO:ret = configure_video_filters(fg, &in_filter, &out_filter);break;case AVMEDIA_TYPE_AUDIO:ret = configure_audio_filters(fg, &in_filter, &out_filter);break;default: av_assert0(0);}if (ret < 0)return ret;if (ost->avfilter) {AVFilterInOut *outputs = avfilter_inout_alloc();AVFilterInOut *inputs  = avfilter_inout_alloc();outputs->name    = av_strdup("in");outputs->filter_ctx = in_filter;outputs->pad_idx = 0;outputs->next    = NULL;inputs->name    = av_strdup("out");inputs->filter_ctx = out_filter;inputs->pad_idx = 0;inputs->next    = NULL;if ((ret = avfilter_graph_parse(fg->graph, ost->avfilter, &inputs, &outputs, NULL)) < 0)return ret;av_freep(&ost->avfilter);} else {if ((ret = avfilter_link(in_filter, 0, out_filter, 0)) < 0)return ret;}if (ost->keep_pix_fmt)avfilter_graph_set_auto_convert(fg->graph,AVFILTER_AUTO_CONVERT_NONE);if ((ret = avfilter_graph_config(fg->graph, NULL)) < 0)return ret;ost->filter = fg->outputs[0];return 0;
}

2.2 使用错误码和错误消息

除了简单的返回值之外,还可以定义一组错误码,并为每个错误码提供描述性的错误消息。可读性更高的错误消息更有助于调试和记录错误,提高排查bug和修复bug的效率。

#define SUCCESS 0
#define ERROR_INVALID_INPUT -1
#define ERROR_OUT_OF_MEMORY -2
// ... 其他错误码 ...const char *error_messages[] = {"Success","Invalid input","Out of memory",// ... 其他错误消息 ...
};int my_function(void)
{int result = /* 执行操作 */;if (result != SUCCESS) {fprintf(stderr, "Error: %s\n", error_messages[-result]);return result;}return SUCCESS;
}

2.3 使用断言(assert)

断言是一种在开发过程中用于检测程序内部错误的方法。它们通常用于验证不应该发生的条件。如果断言失败,程序将终止执行,这有助于在开发阶段捕获逻辑错误。

#include <assert.h>void my_function(int *ptr) {assert(ptr != NULL); // 确保ptr不是空指针// ... 使用ptr执行操作 ...
}

注意:断言应仅用于开发和调试阶段,因为它们会导致程序终止。在生产环境中,应该避免使用断言来处理可能发生的错误情况。

2.4 异常退出时清理已申请的资源

当函数失败或发生异常时,确保释放已分配的资源非常重要。这包括动态分配的内存、打开的文件句柄、锁定的互斥量等。使用goto语句跳转到函数末尾统一处理异常或封装资源管理的函数可以简化资源清理的过程。

void my_function(void)
{int *ptr = malloc(sizeof(int));if (ptr == NULL) {// 处理内存分配失败的情况return;}// ... 执行一些操作 ...free(ptr); // 确保释放内存
}

或者,使用封装了资源管理的函数:

void safe_free(void **ptr) {if (*ptr != NULL) {free(*ptr);*ptr = NULL;}
}void my_function(void) {int *ptr = malloc(sizeof(int));if (ptr == NULL) {// 处理内存分配失败的情况return;}// ... 执行一些操作 ...safe_free((void **)&ptr); // 使用封装函数释放内存
}

2.5 记录日志到文件中

在程序运行时记录关键信息和错误有助于调试和监控程序的行为。可以使用标准库中的fprintf函数将日志消息写入文件或使用专门的日志库(如嵌入式Linux平台最常用的syslog)。

#define LOG_FILE "program.log"void log_message(const char *message) {FILE *file = fopen(LOG_FILE, "a");if (file != NULL) {fprintf(file, "%s\n", message);fclose(file);}
}void my_function(void)
{// ... 执行一些操作 ...if (/* 发生错误 */) {log_message("An error occurred: ...");// 处理错误}
}

2.6 编写清晰的文档和注释

编写清晰的文档和注释有助于其他程序员(包括未来的你)理解代码的预期行为和如何处理异常情况。确保为函数和变量提供描述性的名称,并使用注释来解释复杂或不寻常的代码段。它们能够帮助读者(包括未来的你自己)理解代码的功能、输入、输出以及可能的异常情况。

/**  * @brief 读取文件内容并返回字符串  *  * 这个函数打开指定的文件,读取内容,并返回一个指向内容的字符串指针。  * 如果文件打开失败或读取过程中发生错误,则返回NULL。  *  * @param filename 文件名  *  * @return 指向文件内容的字符串指针,或NULL(如果发生错误)  */  
char* read_file_content(const char* filename) {  FILE *file = fopen(filename, "r"); // 打开文件以读取  if (file == NULL) {  // 打开文件失败,返回NULL  return NULL;  }  fseek(file, 0, SEEK_END); // 定位到文件末尾  long length = ftell(file); // 获取文件长度  fseek(file, 0, SEEK_SET); // 定位回文件开头  char* content = malloc(length + 1); // 分配内存(包括一个终止符'\0')  if (content == NULL) {  // 内存分配失败,关闭文件并返回NULL  fclose(file);  return NULL;  }  size_t bytesRead = fread(content, 1, length, file); // 读取文件内容  if (bytesRead != length) {  // 读取错误,释放内存,关闭文件并返回NULL  free(content);  fclose(file);  return NULL;  }  content[length] = '\0'; // 在字符串末尾添加终止符  fclose(file); // 关闭文件  return content; // 返回指向文件内容的指针  
}

2.7 使用静态分析工具

静态分析工具可以检查代码中的潜在问题,如内存泄漏、未初始化的变量、空指针解引用等。使用像Clang Static AnalyzerCppcheckSplint这样的工具可以帮助发现并修复代码中的错误。

根据以往经验,一个大型项目,经过静态分析工具扫描后,可以提前发现30%的bug。

3. 总结

虽然C语言没有内置的异常处理机制,但通过精心设计返回值、使用错误码和错误消息、断言、资源清理、日志记录编写文档和注释以及静态代码扫描等措施,仍然可以编写出健壮、稳定的C语言程序。这些技巧不仅有助于在开发阶段捕获和修复错误,还能提高程序的可靠性和可维护性。

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

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

相关文章

AI智商排名:Claude-3首次突破100

用挪威门萨&#xff08;智商测试题&#xff09;中 35 个问题对chatGPT等人工智能进行了测试&#xff1a; ChatGPT 对ChatGPT进行了两次挪威门萨测试&#xff0c;在 35 个问题中&#xff0c;它平均答对了 13 个&#xff0c;智商估计为 85。 测试方法 每个人工智能都接受了两次…

<商务世界>《第5课 重组、托管是什么?》

1 托管 1.1 案例 2020年10月&#xff0c;国资委决定&#xff0c;由中国宝武钢铁集团有限公司对中国中钢集团有限公司进行托管&#xff0c;这就意味着中钢集团由一个副部级的央企管理了&#xff0c;虽然级别没有变动&#xff0c;但是他的好多决策都不用先汇报给国资委了&#…

MATLAB知识点:循环语句的经典练习题

​讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自​第4章&#xff1a;MATLAB程序流程控制 下面我们来看…

springmvc的使用方法及运行原理

Spring MVC 是 Spring 框架中用于开发 Web 应用程序的一部分&#xff0c;它基于 MVC&#xff08;Model-View-Controller&#xff09;设计模式&#xff0c;提供了一种灵活且强大的方式来构建 Web 应用。 运行原理&#xff1a; 客户端发送请求&#xff1a;浏览器向服务器发送 H…

贷齐乐错误的waf引起的SQL注入漏洞复现

君衍. 一、环境介绍1、第一道WAF2、第二道WAF 二、环境部署1、模拟源码2、连接数据库源码3、数据库创建4、测试 三、源码分析1、模拟WAF2、注入思路3、PHP下划线特性4、完成假设 四、联合查询注入1、测试回显字段2、爆出库名3、爆出表名4、爆出表下的列名4、爆出flag 一、环境介…

CleanMyMac X4.14.7永久免费Mac电脑清理和优化软件

CleanMyMac X 是一款功能强大的 Mac 清理和优化软件&#xff0c;适合以下几类人群使用&#xff1a; 需要定期清理和优化 Mac 的用户&#xff1a;随着时间的推移&#xff0c;Mac 设备上可能会积累大量的无用文件、缓存和垃圾&#xff0c;导致系统运行缓慢。CleanMyMac X 的智能扫…

【Java JVM】Class 文件

Java 的口号 “一次编写, 到处运行 (Write Once, Run Anywhere)” 的基础: JVM 和 所有平台都统一支持的程序存储格式 – 字节码 (Byte Code)。 只要在对应的平台安装对应的 JVM, 将我们编写的源码编译为 Class 文件, 就能达到了一次编写, 导出运行的目标, 中间的所有细节由不同…

形容passwd和shadow区别

/etc/passwd 存账户信息一般不存密码 /etc/shadow主要用来存密码 /etc/passwd默认是任意用户可读只有root用户可修改 /etc/shadow 默认只有root用户可读可写 /etc/passwd 包含系统用户和用户的主要信息 /etc/shadow 用于储存系统中用户的密码&#xff0c;又称为影子文件 /etc/g…

11.WEB渗透测试-Linux系统管理、安全加固(上)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;10.WEB渗透测试-Linux基础知识-Linux用户权限管理&#xff08;下&#xff09;-CSDN博客 进…

【论文笔记】Language Models are Few-Shot Learners

Language Models are Few-Shot Learners 本部分是 GPT-3 技术报告的第一部分&#xff1a;论文正文、部分附录。 后续还有第二部分&#xff1a;GPT-3 的广泛影响、剩下的附录。 以及第三部分&#xff08;自己感兴趣的&#xff09;&#xff1a;GPT-3 的数据集重叠性研究。 回顾…

部署运维 防火墙,进程 常用命令

防火墙: 1. 查看是否安装了firewalld sudo systemctl status firewalld 查看防火墙状态或者sudo systemctl is-active firewalld 查看防火墙是否是开启状态 2. 开放6379port sudo firewall-cmd --add-port6379/tcp --permanent 刷新防火墙 sudo firewall-cmd --reload 3…

ranger的使用

安装 macOS brew install rangerubuntu sudo apt-get install ranger配置 启动之后ranger会创建一个目录~/.config/ranger 可以使用以下命令复制默认配置文件到这个目录 ranger --copy-configallrc.conf-选项设置和快捷键commands.py-能通过:执行的命令commands_full.py-全套…

产品展示型wordpress外贸网站模板

孕婴产品wordpress外贸网站模板 吸奶器、待产包、孕妇枕头、护理垫、纸尿裤、孕妇装、孕婴产品wordpress外贸网站模板。 https://www.jianzhanpress.com/?p4112 床品毛巾wordpress独立站模板 床单、被套、毛巾、抱枕、靠垫、围巾、布艺、枕头、乳胶枕、四件套、浴巾wordpre…

职场中的团队合作与个人成长

在职场中&#xff0c;团队合作和个人成长是两个不可或缺的要素。一个优秀的团队可以带来更高的工作效率和更好的业绩&#xff0c;而个人的成长则是职场成功的关键。本文将探讨如何在职场中实现团队合作与个人成长的平衡。 一、团队合作的重要性 在职场中&#xff0c;团队合作是…

ARM GNU 汇编 “每日读书“

在GNU ARM汇编程序中&#xff0c;如果我们想定义一个浮点数&#xff0c;那么可以使用下面的伪操作来定义。 标签&#xff0c;命令 f: .float 3.14 .equ f,3.1415 我们可以使用.float 伪操作定义一个浮点数f, 并初始化为3.14 如果你想将这个浮点数重新赋值为3.1415&#xff0c;则…

【Ubuntu】将多个python文件打包为.so文件

1.为什么要将python打包为.so文件&#xff1f; 保护源码 2.实战例子 a.安装相应的包 pip install cython 验证安装是否成功 cython --version b.实战的文件目录和内容 hi.py # This is a sample Python script.# Press ShiftF10 to execute it or replace it with your…

线性代数 --- 特征值与特征向量

特征值与特征向量 已知任意向量x&#xff0c;现有矩阵A对x进行操作后&#xff0c;得到新的向量Ax。这就好比是自变量x与函数f(x)的关系一样&#xff0c;向量x通过类似“函数”的处理得到了一个新的向量Ax。这个新的向量可能和原向量x方向相同&#xff0c;也可能不同(事实上大多…

Java开发工程师面试题(Spring)

一、Spring Bean的生命周期 生命周期可以分为以下几步&#xff1a; 通过Spring框架的beanFactory工厂利用反射机制创建bean对象。根据set方法或者有参构造方法给bean对象的属性进行依赖注入。判断当前bean对象是否实现相关aware接口&#xff0c;诸如beanNameAware、beanFactor…

P1305 新二叉树题解

题目 输入一串二叉树&#xff0c;输出其前序遍历。 输入输出格式 输入格式 第一行为二叉树的节点数n。(1≤n≤26) 后面n行&#xff0c;每一个字母为节点&#xff0c;后两个字母分别为其左右儿子。特别地&#xff0c;数据保证第一行读入的节点必为根节点。空节点用*表示。 …

Java-常见面试题收集(一)

一 Java 基础 1 列举 Java 的 8 大基础类型&#xff0c;并写出对应的占用大小 数据类型占位(字节)数据范围byte1[ -128 , 127 ]short2[ -32768 , 32767 ]int4[ -231 , -231 - 1 ] ≈ [-21 亿&#xff0c;21 亿]long8[ -263 , -263-1 ]float4[-3.4x1038 , ~ 3.4x1038]double8[…