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

浅谈C++中的防御性编程目录
1.什么是防御性编程
2.防御性编程技巧
2.1.采用良好的编码风格
2.2.合理使用assert
2.3.检查函数参数
2.4.使用异常处理
2.5.避免裸指针
2.6.资源管理
2.7.最

目录

1.什么是防御性编程?

2. 防御性编程技巧

2.1. 采用适当的编码风格

2.2. 断言的合理使用

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 关键字。

3)静态关键字

C/C++中静态关键字使用概述_c++静态关键字用法- CSDN博客

函数体内静态变量的作用域是函数体。与自动变量不同,该变量的内存仅分配一次,因此下次调用时其值仍保留为最后一个值。

模块内的静态全局变量可由模块内的所有函数访问,但不能由模块外的其他函数访问。

模块中的静态函数只能被本模块中的其他函数调用,并且该函数仅限于声明它的模块内。

4)按位运算尽量使用、|等运算符,尽量避免使用/、%、*运算符。

5)变量和函数名称应该有意义,并且函数应该只做一件事。

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

7) 在开始编码之前考虑总体设计计划也很重要。

2.2.合理使用assert

C++ 断言Idiom_c++ 断言- CSDN 博客

断言是调试辅助工具,用于在代码中设置检查点。如果条件为真,则程序可以继续执行。如果条件不成立,程序将显示错误消息并退出。这有助于在开发阶段检测错误。

在日常编程中,断言经常被添加到不可预测的逻辑中,从而更容易检测程序中的逻辑错误。例如,以下代码:

//示例1,检查输入参数的有效性

无效函数(int值){

assert(value=0 value=10); //假设值在0 到10 之间。

//句柄值

}

//示例2

使用dealWithFunc=std:functionvoid(const void*, int);

std:mapint,dealWithFunc taskCmds;

布尔函数1(int类型){

auto it=taskCmds.find(type);

if (it==taskCmds.end()){

assert(false); //没有处理这种类型的逻辑。当逻辑到达此点时,它知道某些类型的事件未被处理。

返回假。

}

返回真。

}

2.3.检查函数参数

您的函数应该检查其参数的有效性,并在收到无效参数时采取适当的操作,例如返回错误代码或引发异常。

void process(const char* data, size_t length) {

如果(数据==nullptr){

throw std:invalid_argument(\’数据指针为空\’);

}

如果(长度==0){

throw std:invalid_argument(\’长度不能为零\’);

}

//信息处理

}

2.4.使用异常处理

在C++ 中,异常处理是一个强大的工具,可用于捕获和处理运行时错误。 Try-catch 块允许您优雅地处理异常并避免程序崩溃。

尝试{

//可能抛出异常的代码

} catch (const std: 异常e) {

std:cerr \’Error:\’ e.what() std:endl;

}

2.5.避免裸指针

尽可能使用智能指针(例如std:unique_ptr 和std:shared_ptr)而不是原始指针,以降低内存泄漏和悬空指针的风险。

std:unique_ptrMyClass ptr(new MyClass());

ptr-doSomething();

2.6.资源管理

RAII C++使用思考:资源管理_raii思考- CSDN博客

资源管理(内存、文件句柄等)是C++中的一个重要问题。 RAII(资源获取即初始化)是一种常用的资源管理策略,通过构造函数获取资源并通过析构函数释放资源来正确释放资源。

文件包装类{

公共:

FileWrapper(const std:字符串文件名) {

文件=std:fopen(filename.c_str(), \’r\’);

如果(!文件){

抛出std:runtime_error(\’无法打开文件\’);

}

}

文件包装器(){

如果(文件){

std:f关闭(文件);

}

}

私人:

文件* 文件;

};

2.7.最小化使用全局变量

全局变量可以在程序中随时更改,这使得它们很容易出现难以调试的错误。尝试使用局部变量和参数传递来使代码模块化并且更易于维护。

类我的类{

公共:

无效doSomething() {

int localVar=0;

helperFunction(localVar);

}

私人:

无效helperFunction(int值){

//使用局部变量进行处理

}

};

2.8.封装和模块化

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

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

2)使用局部变量和参数传递:尽量使用局部变量和参数传递,避免使用全局变量。全局变量可以在程序中随时更改,这使得它们很容易出现难以调试的错误。

类我的类{

公共:

无效setValue(int 值) {

如果(值=0){

这个值=值;

} 除此之外{

throw std:invalid_argument(\’值不能为负数\’);

}

}

int getValue() const {

返回值;

}

私人:

整数值;

};

2.9.避免使用宏

宏会使您的代码难以理解和维护。尝试使用常量、内联函数或模板而不是宏。

2.10.初始化所有变量

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

类我的类{

公共:

MyClass() : 值(0) {}

私人:

整数值;

};

2.11.使用范围枚举

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

枚举类Color { 红、绿、蓝};

颜色颜色=Color:Red;

2.12.防止数组越界

访问数组元素时,请确保索引在有效范围内,避免数组越界访问。以下几点很容易犯错误:

1)字符串和字符数组的区别是字符串必须以\’\\0\’结尾,而字符数组必须有长度。例如:

角色名称[7]=\’1234455\’ //\’1234455\’;

char* name=\’1234455\’ //\’1234455\\0\’;

2) 必须包含长度,因为在函数传输过程中数组会退化为指针。如果不包含它,您将不知道长度。

void accessArray(int* arr, size_t 大小, size_t 索引) {

如果(索引=大小){

抛出std:out_of_range(\’索引超出范围\’);

}

//访问arr[索引]

}

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:vectorint vec={1, 2, 3};

vec.push_back(4);

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

2.14.线程安全

深入理解C++锁_C++锁类型- CSDN博客

在多线程环境中,确保代码的线程安全非常重要。使用互斥锁和条件变量等同步机制来避免多个线程访问共享资源时发生争用。

std: 互斥体mtx;

无效线程安全函数(){

std:lock_guardstd: 互斥锁(mtx);

//访问共享资源

}

2.15.代码审查和测试

代码审查

代码审查是检查源代码以发现和修复错误的系统方法。这有助于开发团队提高代码质量,促进团队成员之间的知识共享,并增加团队对软件项目的整体理解。代码审查通常侧重于以下几个方面:

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

测试

测试是验证软件功能、性能和安全性是否满足预期要求的过程。这包括多个级别和类型,例如单元测试、集成测试、系统测试和验收测试。

单元测试:测试软件的最小可测试单元,例如函数或方法。集成测试:测试软件模块之间的交互,以确保它们正确地协同工作。系统测试:测试整个软件系统,验证其是否满足指定的要求。验收测试:由用户或客户执行的测试,以确定软件是否满足他们的需求和期望。

代码审查和测试之间的关系

代码审查和测试是相辅相成的。代码审查允许您在提交代码之前查找并修复错误。另一方面,测试允许您在代码运行时验证其功能和性能。将这两种方法结合起来可以显着提高软件质量和开发效率。

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

综上所述,代码审查和测试是软件开发过程中两个必不可少的环节。它们相辅相成,以确保软件质量、稳定性和安全性。在实际开发过程中,应充分评估和合理使用这两种方法,以提高软件开发效率和软件产品质量。

3.实践中的防御性编程

在现实开发中,防御性编程不仅仅涉及技术问题,还涉及培养编码思维和习惯。以下是一些具体实用的建议。

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

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

持续学习和改进防御性编程是一项需要不断学习和练习的技能。您可以通过阅读相关书籍和博客以及参与技术讨论来不断提高您的防御性编程技能。

4.总结

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

以上关于#C++防御性编程的相关内容摘自网络,仅供参考。相关信息请参见官方公告。

原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/93341.html

Like (0)
CSDN的头像CSDN
Previous 2024年7月6日
Next 2024年7月6日

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注