浅谈C++中的防御性编程

目录

1.什么是防御性编程

2.防御性编程技巧

2.1.采用良好的编码风格

2.2.合理使用assert

2.3.检查函数参数

2.4.使用异常处理

2.5.避免裸指针

2.6.资源管理

2.7.最小化使用全局变量

2.8.封装和模块化

2.9.避免使用宏

2.10.初始化所有变量

2.11.使用范围枚举

2.12.防止数组越界

2.13.使用标准库和智能算法

2.14.线程安全

2.15.代码审查和测试

3.实践中的防御性编程

4.总结


1.什么是防御性编程

        防御性编程是一种编程实践,旨在提高软件的健壮性和可靠性,减少运行时错误。在C++中,这尤其重要,因为C++是一种允许进行低级内存操作和指针操作的语言,这使得它容易出错。

        顾名思义,防御性编程是一种细致、谨慎的编程方法。为了开发可靠的软件,我们要设计系统中的每个组件,以使其尽可能的”保护”自己。我们通过明确地在代码中对设想进行检查,这是一种努力,防止我们的代码以将会展现错误行为的方式被调用。

2.防御性编程技巧

2.1.采用良好的编码风格

1) const关键字

C/C++中const关键字用法总结_返回对象或复合类型的常量引用-CSDN博客

        关键字const可以给读你代码的人传达非常有用的信息。例如,在函数的形参前添加const关键字意味着这个参数在函数体内不会被修改,属于输入参数。

        同时,合理地使用关键字const可以使编译器很自然的保护那些不希望被修改的参数,防止其被无意的代码修改,减少bug的出现。

2) volatile关键字

        在一些并行设备的硬件寄存器(如状态寄存器),中断服务子程序中会访问到的全局变量以及多线程应用中被几个任务共享的变量前使用volatile关键字来防止编译优化。

3) static关键字

C/C++中static关键字用法总结_c++ static 关键词用法-CSDN博客

        函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。

        在模块内的static全局变量可以被模块内的所有函数访问,但不能被模块外其它函数访问。

        在模块内的static函数只可能被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内。

4) 位操作运算中,尽可能使用<<、 >>、 &、|等运算符,尽可能少使用/、%、*运算符。

5) 变量和函数的命名要有意义,并且尽可能做到一个函数只做一件事情。

6) 多采用面向对象的思想来编写代码。

7) 在投入到编码工作之前,先考虑大体的设计方案,这也非常关键。

2.2.合理使用assert

C++之assert惯用法_c++ assert-CSDN博客

        断言(assert)是一种调试辅助工具,用于在代码中设置检查点。如果条件为真,程序可以继续执行;如果条件为假,程序将显示错误消息并终止。这有助于在开发阶段捕获错误。

        在日常编程过程中,可以在无法预知的逻辑当中增加assert判断,很容易发现程序中的逻辑错误。比如下面的代码:

//示例1,检查输入参数的合法性
void func(int value) {
    assert(value >= 0 && value <= 10); // 假设value应该在0到10之间
    // 处理value
}

//示例2
using dealWithFunc = std::function<void(const void*, int)>;
std::map<int, dealWithFunc> taskCmds;

bool func1(int type){
    auto it = taskCmds.find(type);
    if (it == taskCmds.end()){
        assert(false); //没有处理这个type的逻辑, 逻辑一旦走到这里,就知道某个type的事件没有处理
        return false;
    }
    。。。
    return true;
}

2.3.检查函数参数

函数应检查其参数的有效性,并在接收到无效参数时采取适当的行动,如返回错误代码或抛出异常。

void process(const char* data, size_t length) {
    if (data == nullptr) {
        throw std::invalid_argument("data pointer is null");
    }
    if (length == 0) {
        throw std::invalid_argument("length cannot be zero");
    }
    // 处理数据
}

2.4.使用异常处理

在C++中,异常处理是一个强大的工具,可以用于捕获和处理运行时错误。通过使用try-catch块,可以优雅地处理异常情况,避免程序崩溃。

try {
    // 可能抛出异常的代码
} catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << std::endl;
}

2.5.避免裸指针

尽可能使用智能指针(如std::unique_ptrstd::shared_ptr)代替裸指针,以减少内存泄漏和悬挂指针的风险。

std::unique_ptr<MyClass> ptr(new MyClass());
ptr->doSomething();

2.6.资源管理

C++惯用法之RAII思想: 资源管理_raii 思想-CSDN博客

在C++中,资源管理(如内存、文件句柄等)是一个重要的问题。RAII(Resource Acquisition Is Initialization)是一种常用的资源管理策略,通过构造函数获取资源,析构函数释放资源,确保资源的正确释放。

class FileWrapper {
public:
    FileWrapper(const std::string& filename) {
        file = std::fopen(filename.c_str(), "r");
        if (!file) {
            throw std::runtime_error("Unable to open file");
        }
    }


    ~FileWrapper() {
        if (file) {
            std::fclose(file);
        }
    }


private:
    FILE* file;
};

2.7.最小化使用全局变量

全局变量在程序中随时可以被修改,容易引发难以调试的错误。尽量使用局部变量和参数传递,保持代码的模块化和可维护性。

class MyClass {
public:
    void doSomething() {
        int localVar = 0;
        helperFunction(localVar);
    }


private:
    void helperFunction(int value) {
        // 使用局部变量进行处理
    }
};

2.8.封装和模块化

封装是面向对象编程的基本原则之一。通过封装数据和方法,可以减少模块之间的耦合,提高代码的可维护性和可扩展性。

1) 封装数据和方法:通过封装数据和方法,可以减少模块之间的耦合,提高代码的可维护性和可扩展性。

2) 使用局部变量和参数传递:尽量使用局部变量和参数传递,避免使用全局变量,因为全局变量在程序中随时可以被修改,容易引发难以调试的错误。

class MyClass {
public:
    void setValue(int value) {
        if (value >= 0) {
            this->value = value;
        } else {
            throw std::invalid_argument("value cannot be negative");
        }
    }
    int getValue() const {
        return value;
    }
private:
    int value;
};

2.9.避免使用宏

宏可能导致代码难以理解和维护。尽量使用常量、内联函数或模板来代替宏。

2.10.初始化所有变量

确保所有变量在使用前都已初始化,以避免未定义行为。

class MyClass {
public:
    MyClass() : value(0) {}


private:
    int value;
};

2.11.使用范围枚举

使用范围枚举(enum class)代替传统的枚举,以避免枚举值的隐式转换和名称冲突。

enum class Color { Red, Green, Blue };
Color color = Color::Red;

2.12.防止数组越界

在访问数组元素时,确保索引在有效范围内,避免数组越界访问。非常容易搞错的有以下几点:

1)字符串和字符数组的区别,字符串必须是以'\0'结束,字符数组必须带长度。比如:

char name[7] = "1234455"; // "1234455"
char* name = "1234455";   // "1234455\0"

2)数组在函数传递的过程中会退变成指针,所以必须带长度,不然不知道长度

void accessArray(int* arr, size_t size, size_t index) {
    if (index >= size) {
        throw std::out_of_range("index out of range");
    }
    // 访问arr[index]
}

int a[] = {1,44,56,7,8,9,9};
accessArray(a, sizeof(a)/sizeof(a[0]), 5);

2.13.使用标准库和智能算法

C++标准库提供了多种容器(如std::vector、std::map等),它们封装了复杂的数据结构和操作,能有效避免内存泄漏和指针错误。

std::vector<int> vec = {1, 2, 3};
vec.push_back(4);

std::sort(vec.begin(), vec.end());

2.14.线程安全

深入理解C++中的锁_c++锁的类型-CSDN博客

在多线程环境中,确保代码的线程安全是至关重要的。使用互斥锁(mutex)、条件变量(condition variable)等同步机制,确保多个线程访问共享资源时不会产生冲突。

std::mutex mtx;
void threadSafeFunction() {
    std::lock_guard<std::mutex> lock(mtx);
    // 访问共享资源
}

2.15.代码审查和测试

代码审查

代码审查(Code Review)是一种通过检查源代码来找出并修正错误的系统性方法。它可以帮助开发团队提高代码质量,促进团队成员之间的知识共享,以及增强团队对软件项目的整体理解。代码审查通常关注以下几个方面:

  • 代码质量:检查代码是否遵循了编码规范,是否存在潜在的错误或不合理的设计。
  • 可读性:评估代码是否易于理解,变量、函数和类的命名是否清晰。
  • 性能:分析代码的执行效率,是否存在性能瓶颈。
  • 安全性:检查代码是否存在安全漏洞,如SQL注入、跨站脚本(XSS)等。

测试

测试是验证软件功能、性能和安全性是否符合预期要求的过程。它包括多个层次和类型,如单元测试、集成测试、系统测试和验收测试等。

  • 单元测试:针对软件中的最小可测试单元(如函数或方法)进行的测试。
  • 集成测试:测试软件模块之间的交互,确保它们能够正确地协同工作。
  • 系统测试:将整个软件系统作为一个整体进行测试,验证其是否满足规定的需求。
  • 验收测试:由用户或客户进行的测试,以确认软件是否满足他们的需求和期望。

代码审查与测试的关系

代码审查和测试是相互补充的。代码审查可以在代码提交之前发现并修正错误,而测试则可以在代码运行期间验证其功能和性能。结合使用这两种方法可以显著提高软件的质量和开发效率。

  • 提高代码质量:通过代码审查,可以及早发现并修正潜在的错误和不合理的设计。而测试则可以进一步验证代码的正确性和稳定性。
  • 促进知识共享:代码审查是一个团队成员之间相互学习和交流的过程,有助于提升整个团队的技术水平。测试则可以让团队成员更深入地了解软件的功能和性能。
  • 增强软件安全性:代码审查和测试都可以帮助发现软件中的安全漏洞,并及时进行修复,从而增强软件的安全性。

        综上所述,代码审查和测试是软件开发过程中不可或缺的两个环节。它们相互补充,共同确保软件的质量、稳定性和安全性。在实际开发过程中,应该充分重视并合理运用这两种方法,以提高软件开发效率和软件产品质量。

3.实践中的防御性编程

        在实际开发中,防御性编程不仅仅是一个技术问题,更是一种编码思维和习惯的养成。以下是一些具体的实践建议:

        代码审查定期进行代码审查,发现潜在的错误和问题。通过集体智慧,可以提高代码的质量和健壮性。

        编写单元测试单元测试可以帮助验证代码的正确性,捕获边界条件和异常情况。编写全面的单元测试是防御性编程的重要组成部分。

        持续学习和改进防御性编程是一门需要不断学习和实践的艺术。通过阅读相关书籍、博客和参加技术讨论,可以不断提高自己的防御性编程水平。

4.总结

        通过实施这些防御性编程策略,你可以提高C++代码的质量,减少错误和漏洞,从而创建更可靠、更易于维护的软件,更重要的是减少软件交付的时间,节约开发成本。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

二维树状数组区域查询

落谷4514 过关代码如下 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using namespace std; //#define int long longconst int N 2050; int t1[N][N], t2[N][N], t3[N][N], t4[N][N]; int lowbit(int x) { return x & (-x); } int n, m; void update(…

C++ 对象模型 -- vptr 和 vtbl

是看侯捷老师讲解c对象模型 虚表和虚指针的笔记和程序验证。 先看两张关键的图吧&#xff0c;右边的三个基类和派生类 A&#xff0c;B&#xff0c;C。定义了两个虚函数&#xff0c;两个一般成员函数&#xff0c;以及几个成员变量。 只有在类中有虚函数时&#xff0c;才会有虚指…

LT8711UXE2 国产芯片 Type-C with 2lane@8.1Gbps/lane 4K60 USB3.0 在线提供软硬件技术支持服务

2.一般说明 LT8711UXE2是一款高性能的Type-C/DP1.4到HDMI2.0转换器&#xff0c;设计用于将USBType-C源或DP1.4源连接到HDMI2.0收发器。该LT8711UXE2集成了一个符合DP1.4标准的接收器和一个符合HDMI2.0标准的发射器。此外&#xff0c;还包括用于CC通信的两个CC控制器&#xff0c…

深入解析代理模式:静态代理与动态代理的比较及JDK与CGLIB动态代理技术

1. 静态代理与动态代理的区别 静态代理和动态代理都是实现代理模式的方式&#xff0c;它们在实现上有很大的不同。下面是它们的主要区别&#xff1a; 实现方式不同 静态代理 静态代理是在编译期就已经确定代理对象的类型。代理类需要手动编写&#xff0c;并实现被代理类的接…

C++20中的基于范围的for循环(range-based for loop)

C11中引入了对基于范围的for循环(range-based for loop)的支持&#xff1a;该循环对一系列值(例如容器中的所有元素)进行操作。代码段如下&#xff1a; const std::vector<int> vec{ 1,2,3,4,5 }; for (const auto& i : vec)std::cout << i << ", …

免费鼠标连点器有吗?需要付费吗?鼠标连点器电脑版免费推荐6款!

在数字化时代&#xff0c;鼠标连点器成为了许多用户提高工作效率、优化游戏体验的得力助手。然而&#xff0c;面对市场上琳琅满目的鼠标连点器软件&#xff0c;很多用户都会产生疑问&#xff1a;是否有免费的鼠标连点器&#xff1f;它们真的需要付费吗&#xff1f;今天&#xf…

转盘输入法-单独鼠标版本

序 转盘输入法&#xff0c;给你的聊天加点新意。它不用常见的九宫格或全键盘&#xff0c;而是把字母摆在圆盘上&#xff0c;一滑一滑&#xff0c;字就出来了&#xff0c;新鲜又直接。 单独鼠标版本GIF演示 演示软件下载 转盘输入法https://download.csdn.net/download/u0146…

优化LabVIEW代码以提高软件性能

优化LabVIEW代码对于提高软件性能、减少执行时间和资源消耗至关重要。以下是一些具体的策略和方法&#xff0c;可以帮助LabVIEW程序员优化代码&#xff1a; 1. 代码结构和模块化 使用子VI&#xff1a;将重复使用的代码段封装成子VI&#xff0c;提高代码的可读性和可维护性。 避…

为什么https比http更安全

读完本文&#xff0c;希望你能明白&#xff1a; HTTP通信存在什么问题HTTPS如何改进HTTP存在那些问题HTTPS工作原理是什么 一、什么是HTTPS HTTPS是在HTTP上建立SSL加密层&#xff0c;并对传输数据进行加密&#xff0c;是HTTP协议的安全版。现在它被广泛用于万维网上安全敏感…

Python酷库之旅-第三方库Pandas(005)

目录 一、用法精讲 7、pandas.read_clipboard函数 7-1、语法 7-2、参数 7-3、功能 7-4、返回值 7-5、说明 7-6、用法 7-6-1、代码示例 7-6-2、结果输出 8、pandas.DataFrame.to_clipboard函数 8-1、语法 8-2、参数 8-3、功能 8-4、返回值 8-5、说明 8-6、用法…

C语言自定义类型——联合体、枚举

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、联合体&#xff08;一&#xff09;、联合体的声明&#xff08;二&#xff09;、联合体的特点&#xff08;三&#xff09;、联合体大小的计算&#xff01;&a…

提取重复数据

直接上控制台代码&#xff1a; Module Module1Sub Main()Console.WriteLine("请输入数据&#xff0c;以""&#xff0c;""相隔&#xff1a;")Dim str As String Console.ReadLineDim result From x In str.Split(",")Group By x Int…

【吊打面试官系列-MyBatis面试题】MyBatis 实现一对一有几种方式?具体怎么操作的?

大家好&#xff0c;我是锋哥。今天分享关于 【MyBatis 实现一对一有几种方式?具体怎么操作的&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; MyBatis 实现一对一有几种方式?具体怎么操作的&#xff1f; 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询…

Springboot助农农产品销售系统-计算机毕业设计源码16718

摘要 SpringBoot助农农产品销售系统旨在通过利用SpringBoot框架开发一个便捷高效的农产品销售平台。该系统包括用户注册登录、商品浏览、购物车管理、订单生成、支付功能等模块。通过整合支付接口、地图定位、推荐系统等技术&#xff0c;提供给用户更好的购物体验。本文介绍了…

宿舍报修小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;基础数据管理&#xff0c;论坛管理&#xff0c;故障上报管理&#xff0c;新闻信息管理&#xff0c;维修人员管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;新闻信息…

B组亚太赛数学建模

问题1 1.对训练数据集进行数据清洗&#xff0c;处理缺失值和异常值。 2.采用散点图作为可视化手段。 3.采用皮尔逊相关系数进行相关性分析。 4.提出预防措施。 问题2 1.采用k-means聚类算法将洪水概率分为高中低三个群组。 2.通过线性回归模型计算特征权重。 3.选择特定…

SpringBoot | 大新闻项目源码打包

对于一个完成好的后端项目&#xff0c;如何进行打包发送给其他人&#xff0c;在电脑上进行查看 1.在pom.xml添加&#xff1a; <build><plugins> <!-- 打包插件--><plugin><groupId>org.springframework.boot</groupId><art…

刷题之移除元素(leetcode)

移除元素 这题简单题&#xff0c;但是前面思路是先找到左边第一个不是val的&#xff0c;和右边第一个不是val的&#xff0c;进行交换&#xff0c;边界条件没有处理好&#xff0c;导致报错&#xff08;水平真菜&#xff09; 也可以直接把left是val的与right进行交换&#xf…

CTFHUB-SSRF-302跳转 Bypass

开启题目&#xff0c;页面空白 尝试访问127.0.0.1/flag.php页面 ?url127.0.0.1/flag.php 提示&#xff1a;不允许企业内部IP访问&#xff0c;使用file协议获取其源码&#xff0c;得到flag.php页面源码 ?urlfile:///var/www/html/flag.php 与之前一样&#xff0c;通过REMOT…

将循环转化为递归的三种方法,求1+2+3……+n等差数列

解法一&#xff1a;使用公共变量s&#xff0c;递归循环1~n加到s上 #include<bits/stdc.h> using namespace std; int n,s; void fun(int i){if(i<n){ssi;fun(i1);}}int main(){cin>>n;fun(1);cout<<s;return 0; } 解法二&#xff1a;通过层层累加&#x…