c++ 指针的安全问题

指针是一个强大的工具,但它们可能导致多种安全问题。接下来我们一起研究一下会出现的安全问题。欢迎大家补充说明!!!

悬挂指针(也称为悬空指针或迷途指针)

是指向一块已经释放或无效内存的指针。悬挂指针的出现通常是由于对象被删除或释放后,有指针仍然保留着指向该内存地址的引用,但此时该地址上的数据已经不再有效或被分配给其他用途。

下面是悬挂指针出现的典型场景及其危险性:

int* ptr = new int(10); // 动态分配一个整数
delete ptr; // 释放内存
// ptr 现在成为悬挂指针,因为它指向被释放的内存

delete操作之后,ptr仍然保存着之前分配的内存地址,但这块内存已经被归还给操作系统或用来存放其他内容,在这段内存上进行读写操作会导致不可预期的行为,包括数据损坏和程序崩溃。

悬挂指针的危险性

1、未定义的行为:对悬挂指针解引用或在其上进行操作可能导致未定义行为,这意味着程序可能会崩溃,或者更糟糕的是,静默地继续运行并产生错误数据。
2、安全漏洞:悬挂指针可能会被恶意利用,导致安全漏洞,如缓冲区溢出和其他类型的攻击。

如何避免悬挂指针

1、及时清空指针:释放内存后,立即将指针设置为nullptr,这样任何对指针的后续访问都将是对空指针的访问,这虽然不能解引用,但至少避免了悬挂指针的问题。
	delete ptr;ptr = nullptr; // 避免悬挂指针
2、局部变量的生命周期:尽量使用局部变量(包括RAII对象和智能指针)来管理资源,这样当变量离开作用域时,资源就会自动释放。
3、智能指针的使用:使用C++的智能指针(如std::unique_ptr和std::shared_ptr),它们能够自动管理内存,确保对象的生命周期得到适当的控制。
4、避免原始指针:在不需要低级内存操作的情况下,尽量避免使用原始指针。

野指针(也称为悬空指针)

指向不可预测内存位置的指针。它通常是由于指针没有被初始化、已经释放或已经删除的内存的指针。

野指针的危险性在于,它可能指向任何地方,包括无效或者保留的内存区域。对野指针的解引用是非法的,它会导致未定义的行为,可能会引发程序崩溃或者更糟糕的后果。与悬挂指针类似,野指针也有可能被用于攻击,从而导致安全漏洞。

总之野指针问题通常源于不当的内存管理。在C++程序设计中,应该密切关注指针的使用,确保它们在任何时刻都指向有效的内存地址或者为nullptr。

野指针产生的情况

1、未被初始化的指针:
int* ptr; // 未初始化的指针
*ptr = 5; // 未定义行为,对野指针解引用
2、指针释放后遗忘置空:
int* ptr = new int(10); // 动态分配内存
delete ptr; // 释放内存
// 如果在此后没有将 ptr 置为 nullptr,它将变成野指针

避免野指针的策略:

1、始终初始化指针: 声明指针时,应该立即将其初始化为nullptr,或者给它一个有效的内存地址。
int* ptr = nullptr; // 安全地初始化为nullptr
2、分配内存后进行检查: 在使用动态内存分配(如 new 或 malloc)后,检查是否分配成功,并在内存分配失败时采取适当的错误处理。
3、释放内存后立即置空: 释放指针指向的内存后,立即将指针置为nullptr。
delete ptr;
ptr = nullptr; // 避免野指针
4、使用智能指针: 尽量使用标准库中的std::unique_ptr或std::shared_ptr等智能指针,它们会自动管理生命周期,从而避免野指针问题。
5、避免复杂的指针操作: 尽量简化使用指针的代码,避免不必要的指针操作,减少出错机率。
6、代码审查和静态分析: 使用代码审查和静态分析工具来检测潜在的野指针使用。

内存泄漏

内存泄漏指的是程序在运行过程中分配了内存(通过 new、malloc 等),但没有适时释放(使用 delete 或 free),导致已分配的内存既不被使用也无法被回收。这会导致程序的内存消耗持续增加,最终可能耗尽系统资源,影响程序的性能,甚至导致程序崩溃

示例
函数 createMemoryLeak 分配了一个整型数的内存,但并没有释放它。因此,当函数执行完毕后,分配的内存空间由于没有指针引用它,变得无法访问,导致内存泄漏。
#include <iostream>void createMemoryLeak() {int* leakyInt = new int(42); // 动态分配,没有相应的 delete
}int main() {createMemoryLeak(); // 调用会产生内存泄漏的函数// ... 程序其他代码return 0;// 程序结束后,由于缺乏 delete,分配的内存被泄漏
}
避免内存泄漏的方法
1、使用智能指针:C++11以后,推荐使用智能指针 (如 std::unique_ptr 和 std::shared_ptr) 来自动管理内存。智能指针在销毁时会自动释放它们所拥有的资源,有效防止内存泄漏。
    #include <memory>void safeFunction() {auto safePtr = std::make_unique<int>(42); // 使用 unique_ptr,无需手动释放}
2、遵循 RAII 原则:资源获取即初始化(Resource Acquisition Is Initialization) 原则是现代 C++ 程序设计的基础。确保资源(如内存、文件句柄等)的获取和释放始终与对象的生命周期保持一致。
3、仔细管理裸指针:如果不得不使用裸指针,应确保每个 new 与一个 delete 相对应,每个 new[] 与一个 delete[] 相对应。
4、尽量使用标准库容器:例如 std::vector、std::string 等,它们管理内存的生命周期,减少了手动内存管理的需求。
5、代码审查和自动化测试:通过工具和测试来检测代码中的内存泄漏,写出更健壮的代码。
6、使用内存泄漏检测工具:使用像 Valgrind、AddressSanitizer,或者 Visual Studio 的内存检测工具来帮助发现和锁定内存泄漏的位置。
内存泄漏检测工具
Valgrind: 一个广泛使用的Linux下的内存检测工具,可以帮助发现内存泄漏以及其他内存相关错误。
AddressSanitizer(ASan): 一个快速的内存错误检测器,可以检测出包括内存泄漏在内的各种内存访问错误。
Visual Studio: 提供了内置的内存泄漏检测工具,在调试模式下运行程序时可以帮助发现内存泄漏。

缓冲区溢出

缓冲区溢出(buffer overflow)是一种安全漏洞,它发生在程序试图将数据写入一个固定长度的缓冲区,而写入的数据量超过了缓冲区可以容纳的大小。由于 C++ 不会自动检查数组边界,所以当进行数组操作时,如果不慎,很容易发生缓冲区溢出。

缓冲区溢出可能导致程序崩溃、数据损坏,甚至让攻击者有机会执行任意代码,这在某些情况下会导致安全漏洞。

缓冲区溢出示例
buffer 数组定义为长度 10,但在 for 循环中,我们写入了 11 个字符(从 0 到 10,包括两端),因此,当 i 为 10 时,我们会尝试写入数组的边界之外,这就会导致缓冲区溢出。
#include <iostream>void bufferOverflowExample() {char buffer[10];for(int i = 0; i <= 10; i++) {buffer[i] = 'a'; // i = 10 时会导致缓冲区溢出}
}int main() {bufferOverflowExample(); // 调用函数,演示缓冲区溢出return 0;
}
防止缓冲区溢出:
1、使用标准库容器: 例如 std::vector 和 std::string 来替代原始数组。这些容器会自动地管理内存,并提供边界检查操作。
2、边界检查: 如果必须使用原始数组,确保对所有的数组访问操作进行边界检查。
3、使用安全函数: 尽量使用安全的函数来代替旧的函数。比如使用 std::strncpy 代替 strcpy,使用 std::snprintf 代替 sprintf。
4、堆栈保护: 现代编译器通常提供选项以增加堆栈保护,可以帮助阻止缓冲区溢出攻击。
5、避免不安全的函数: 一些函数本身就存在安全隐患,比如 gets 函数,应当避免使用。
6、代码审查: 持续审查代码,查找可能的缓冲区溢出风险。
7、动态检测工具: 使用动态内存调试工具,如 Valgrind 或 AddressSanitizer,它们可以在开发过程中帮助发现缓冲区溢出。
8、静态代码分析: 许多现代 IDE 和静态代码分析工具可以扫描源代码,查找潜在的缓冲区溢出风险。
9、使用堆分配: 当分配大型数据结构时,使用堆 (通过 new 或 std::make_unique 等) 而非栈,以避免栈溢出。
10、开启编译器安全检查: 某些编译器提供编译时检查,可以帮助检测数组边界问题。
典型的编译器安全选项
GCC/Clang 有 -fstack-protector 选项开启堆栈保护。
Visual Studio 提供 /GS 以增加安全检查。

释放非动态分配的内存

尝试释放非动态分配的内存(即没有使用 new 或者 malloc 系列函数分配的内存)是一个严重的编程错误。这种错误会导致未定义行为(undefined behavior),这意味着程序可能会崩溃、损坏数据、运行异常或者有时看似正常运行,使得问题难以跟踪和调试。

非动态分配内存示例

指针 ptr 指向的是栈上分配的变量 i 的地址,而不是通过 new 关键字分配的堆内存。尝试用 delete 释放栈上的变量是不允许的,这会导致程序运行出现未定义行为。

int main() {int i = 42;int* ptr = &i;// 错误:试图释放一个非动态分配的指针delete ptr; // 未定义行为return 0;
}
正确的动态内存分配和释放

只有通过 new 分配的内存才应该用 delete 释放,通过 new[] 分配的内存应该用 delete[] 释放。类似地,malloc 分配的内存应该用 free 释放。

int main() {// 正确:使用 new 分配动态内存int* ptr = new int(42);// 使用指针做一些事情...// 正确:使用 delete 释放动态分配的内存delete ptr;return 0;
}
如何避免这种错误
1、使用智能指针:智能指针如 std::unique_ptr 或 std::shared_ptr 可以自动管理内存的释放,这样就不需要手动调用 delete2、尽量避免裸指针:如果可以,尽量使用栈分配或者标准库容器类,比如 std::vector 或 std::string,这样就不需要直接处理内存分配和释放。
3、代码审查和自动化测试:通过代码审查和测试可以帮助检测和防止这类错误。
4、清晰的所有权管理:在设计软件时,清晰地定义哪部分代码负责分配和释放内存。
5、使用 RAII (Resource Acquisition Is Initialization) 原则:封装资源管理在对象内,利用构造函数分配和析构函数释放资源,保证资源的正确管理。
6、编写自动化测试:通过自动化测试来检测内存管理的问题,确保在释放之前内存是通过正确的方式分配的。

前越界和后越界访问

在c++中,数组的前越界访问和后越界访问是两种常见的内存安全错误。它们都属于缓冲区溢出(buffer overflow)的范畴。

后越界访问

后越界访问发生在当你尝试访问一个数组或缓冲区结束之后的内存位置时。常见的情况是,访问数组时超出其已定义的长度范围。这种错误可能引起程序崩溃或行为异常,并且可以被恶意利用来攻击软件系统。

int arr[5] = {0, 1, 2, 3, 4};
// 越界访问:访问数组后面的元素
int value = arr[5]; // 错误,arr[5] 实际上是 arr 数组后的第一个位置
前越界访问

前越界访问发生在访问数组或缓冲区开始之前的内存位置时。这种情况不像后越界那么常见,但同样会导致未定义行为。

int arr[5] = {0, 1, 2, 3, 4};// 越界访问:访问数组前面的元素
int value = *(arr - 1); // 错误,arr - 1 指向数组前面的位置
如何避免越界访问
1、使用标准库容器:使用 std::vector, std::array, std::string 等 STL 容器,它们提供了边界检查的方法(如 at() 成员函数),如果越界,它们会抛出异常。
2、边界检查:总是在数组操作之前执行边界检查,确保索引是有效的。
3、使用迭代器和范围基 for 循环:尽量使用迭代器或者范围基 for 循环进行数组或容器的遍历,这样可以避免直接操作索引。
4、静态和动态分析工具:利用静态分析工具(诸如 Clang Static Analyzer 或者 cppcheck)和动态分析工具(比如 Valgrind 或者 AddressSanitizer)检测代码中可能存在的越界访问。
5、自动化测试和代码审查:编写测试用例以捕获边界条件,并进行代码审查以确保遍历操作的正确性。
6、避免裸指针操作:尽量减少指针算术操作,如果不得不使用,确保操作是安全的。
7、初始化和清零:对于本地数组,可在声明时对它们进行初始化或清零,以避免不小心使用未初始化的内存。
注意:
C++ 标准库容器的 operator[] 方法通常不会做边界检查,而是假设你已经知道你在做什么。这使得这个操作更加高效,但也更危险。如果你需要边界检查,请使用容器的 at() 方法,它会在越界时抛出 std::out_of_range 异常。

异常和错误处理不当

在 C++ 编程中,异常和错误处理是关键的程序设计考量。不当的异常和错误处理可能会导致资源泄露、程序崩溃、不一致的状态和安全漏洞。以下是一些常见的异常和错误处理不当的情况和如何避免它们。

1. 不捕获可能抛出异常的代码

当你的代码调用可能抛出异常的函数或方法,而却没有捕获异常,可能会导致程序的非正常终止。
解决方法:使用 try-catch 块包围可能抛出异常的代码,并根据可能发生的错误类型来处理异常。

2. 过宽泛的异常捕获

使用过宽泛的异常捕获(如捕获所有类型的异常)可能会掩盖问题的真正原因,并导致调试困难。
解决方法:尽量捕获特定的异常类型,这有利于更精确地处理错误并提供更有用的调试信息。

3. 异常处理中忽略资源释放

在异常处理过程中,如果没能正确释放资源(如动态分配的内存、文件句柄、锁等),将会导致资源泄露。
解决方法:利用 RAII(Resource Acquisition Is Initialization)原则管理资源,使用智能指针如 std::unique_ptr 和 std::shared_ptr 管理动态分配的内存,使用 std::lock_guard 和 std::unique_lock 管理锁,确保在栈展开(stack unwinding)时资源可以被自动释放。

4. 安静捕获和吞噬异常

在 catch 块中什么都不做(或者只打印错误消息)然后继续执行,有可能导致程序在一个不确定的状态下继续运行。
解决方法:在 catch 块中恰当地处理异常。如果不能处理,应该再次抛出或终止程序。

5. 在构造函数和析构函数中抛出异常

如果构造函数抛出异常,在没有完全构建该对象的情况下,析构函数不会被调用,可能会导致资源泄露。此外,在析构函数中抛出异常,如果同时有其它异常抛出,可能会导致 std::terminate 被调用。
解决方法:保证构造函数中的代码能够安全地处理异常,不要在析构函数中抛出异常。

6. 检查错误代码

一些 C++ 的库和接口使用返回值来表示成功或错误。忽略这些返回值可能会导致错误未被检测到。
解决方法:总是检查函数返回的错误代码,并根据错误代码进行相应的处理。

7. 使用异常作为普通流程控制

异常应该仅用于异常情况,它们不应该被用作执行普通流程控制,因为这样会降低程序性能,并且会让程序逻辑更难以理解。
解决方法:只针对确实异常的情况抛出和处理异常,使用其他方式(如条件判断)来处理正常逻辑。

8. 内存不足异常 std::bad_alloc

在使用动态内存分配时,例如 new 操作符可能会抛出 std::bad_alloc。不处理这种异常可能会导致程序崩溃。
解决方法:对可能抛出 std::bad_alloc 的代码使用 try-catch 块并适当处理,或者使用 noexcept 的 new (std::nothrow) 形式。

9. 错误的异常规格说明(Exception Specification)

C++11 之前,异常规格说明(如 throw(Type))用于表明函数可能抛出的异常类型。在 C++11 后,这个语法被 noexcept 取代。
解决方法:尽量避免使用异常规格说明,转而使用 noexcept 关键字,这能提供更明确的异常安全保证。

10. 递归异常

在 catch 代码块中抛出一个新的异常,而没有释放捕获的异常,将导致异常递归,可能消耗过多资源甚至栈溢出。
解决方法:避免在 catch 块内抛出新的异常,如果必要,应该在之前将异常释放或转换为适当的类型。
通过积极地关注异常和错误处理,可以提高程序的可靠性、可维护性,并且减少潜在的安全风险。

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

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

相关文章

Chrome 浏览器 Manifest V3 版本中 scripting API 解析

Chrome 浏览器 Manifest V3 版本中 scripting API 解析 chrome.scripting 使用 chrome.scripting API 在不同上下文中执行脚本。 可以使用 chrome.scripting API 将 JavaScript 和 CSS 注入网站。 一、所需权限 scripting 二、Manifest 配置 使用 chrome.scripting API&…

DC电源模块与AC电源模块的对比分析

DC电源模块与AC电源模块的对比分析 BOSHIDA DC电源模块和AC电源模块是两种常见的电源模块&#xff0c;它们在供电方式、稳定性、适用范围等方面有所不同&#xff0c;下面是它们的对比分析&#xff1a; 1. 供电方式&#xff1a; DC电源模块通过直流电源供电&#xff0c;通常使用…

【Linux】Linux 系统编程——which 命令

文章目录 1.命令概述2.命令格式3.常用选项4.相关描述5.参考示例 1.命令概述 which 命令用于定位执行文件的路径。当输入一个命令时&#xff0c;which 会在环境变量 PATH 所指定的路径中搜索每个目录&#xff0c;以查找指定的可执行文件。 2.命令格式 which [选项] 命令名3.常…

美易投资:在经济不确定性中寻求避风港:美股投资者转向高质量股

随着美国经济衰退的阴云逐渐笼罩&#xff0c;市场的不确定性增加&#xff0c;投资者开始寻找更为稳健的投资策略。在这种背景下&#xff0c;高质量股票成为了市场的新宠。这些股票通常具备稳定的财务状况、低债务水平和充裕的现金储备&#xff0c;使它们在经济放缓时期能够展现…

生产力与生产关系 —— 浅析爱泼斯坦事件 之 弱电控制强电原理

据网络文字与视频资料&#xff0c;爱泼斯坦事件是犹太精英阶层&#xff0c;为了掌控美国国家机器为犹太利益集团服务&#xff0c;而精心设下的一个局。本文先假设这个结论成立&#xff0c;并基于此展开讨论。 我们知道&#xff0c;弱电管理强电是电气工程中的一门专门学问&…

Mysql 数据库DDL 数据定义语言——数据库,数据表的创建

DDL&#xff1a;数据定义语言&#xff0c;用来定义数据库对象&#xff08;数据库&#xff0c;表&#xff0c;字段&#xff09;—Database Definition Language 1、登录数据库&#xff0c;输入用户名和密码 mysql -ufdd -p990107Wjl2、查看数据库 show databases;3、创建一个…

MySQL面试题 | 12.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

什么情况下物理服务器会运行出错?

​  物理服务器&#xff0c;也称为裸机服务器&#xff0c;一般可以提供高性能计算水平和巨大的存储容量。然而&#xff0c;它们也难免会遇到一些问题。运行出错时&#xff0c;可能会导致停机和数据丢失。在这篇文章中&#xff0c;介绍了常见的物理服务器在一些情况下显示出错…

【白话机器学习的数学】读书笔记(2)学习回归

二、学习回归 1. y y y与 f θ ( x ) f_\theta(x) fθ​(x) y y y 是实际数据x对应的值 f θ ( x ) f_\theta(x) fθ​(x)是我们构造出来的函数&#xff0c;例如 f θ ( x ) θ 0 θ 1 x f_\theta(x) \theta_0 \theta_1 x fθ​(x)θ0​θ1​x 所以我们希望这两个越接近&…

【视觉SLAM十四讲学习笔记】第五讲——相机模型

专栏系列文章如下&#xff1a; 【视觉SLAM十四讲学习笔记】第一讲——SLAM介绍 【视觉SLAM十四讲学习笔记】第二讲——初识SLAM 【视觉SLAM十四讲学习笔记】第三讲——旋转矩阵 【视觉SLAM十四讲学习笔记】第三讲——旋转向量和欧拉角 【视觉SLAM十四讲学习笔记】第三讲——四元…

苹果MAC怎么清理内存?苹果MAC清理内存的方法

很多使用苹果电脑的用户都喜欢在同时运行多个软件&#xff0c;不过这样会导致在运行一些大型软件的时候出现不必要的卡顿现象&#xff0c;这时候我们就可以去清理下内存&#xff0c;不过很多人可能并不知道正确的清内存方式&#xff0c;下面就和小编一起来看看吧。 苹果MAC清理…

Shell脚本同时调用#!/bin/bash和#!/usr/bin/expect

如果你想在一个脚本中同时使用bash和expect&#xff0c;你可以将expect部分嵌入到bash脚本中。以下是一个示例&#xff1a; #!/bin/bash# 设置MySQL服务器地址、端口、用户名和密码 MYSQL_HOST"localhost" MYSQL_PORT"3306" MYSQL_USER"your_usernam…

Maven 依赖传递和冲突、继承和聚合

一、依赖传递和冲突 1.1 Maven 依赖传递特性 1.1.1 概念 假如有三个 Maven 项目 A、B 和 C&#xff0c;其中项目 A 依赖 B&#xff0c;项目 B 依赖 C。那么我们可以说 A 依赖 C。也就是说&#xff0c;依赖的关系为&#xff1a;A—>B—>C&#xff0c; 那么我们执行项目 …

PDF有编辑密码怎么办

目录 注意&#xff1a; windows方法&#xff1a; 1 python 下载 2 打开命令行 3 安装 pikepdf 4 编写python脚本 5 使用py脚本 6解密完成 Linux方法&#xff1a; 注意&#xff1a; 此方法可以用于破解PDF的编辑密码&#xff0c;而不是PDF的打开密码 当遇到类似如下问…

MySQL语句 | 在MySQL中解析JSON或将表中字段值合并为JSON

MySQL提供了一系列的JSON函数来处理JSON数据&#xff0c;包括从JSON字符串中提取值和将表中字段值合并为JSON等。 在MySQL中解析JSON 可使用JSON_EXTRACT函数提取JSON字符串中指定字段的值&#xff0c;使用JSON_UNQUOTE函数去除提取的字符串值周围的引号&#xff0c;以得到原…

openssl3.2 - 官方demo学习 - signature - rsa_pss_direct.c

文章目录 openssl3.2 - 官方demo学习 - signature - rsa_pss_direct.c概述笔记END openssl3.2 - 官方demo学习 - signature - rsa_pss_direct.c 概述 用RSA私钥签名 d2i_PrivateKey_ex()可以从内存载入私钥数据, 得到私钥EVP_PKEY* 从私钥产生ctx, 对ctx进行签名初始化, 设置…

mavavi显示 3d姿态

目录 mayavi安装&#xff1a; mavavi显示 3d姿态 mayavi安装&#xff1a; 第1步 从这里下载两个whl文件&#xff0c; https://www.lfd.uci.edu/~gohlke/pythonlibs/ * mayavi&#xff1a;*xxx.whl * vtk: VTK‑9.1.0‑cp310‑cp310‑win_amd64.whl 第2步 pip install py…

linux-挂载Samba共享

linux-挂载Samba共享 1、linux服务器启动Samba共享服务 2、客户端电脑安装cifs-utils dnf install cifs-utils # 或 yum install cifs-utils3、挂载共享目录 # 创建挂目录 mkdir /share # 使用mount命令挂在共享目录&#xff0c;-t协议类型 -o用户名密码 共享目录访问地址 挂…

无监督学习 - 均值聚类(K-Means Clustering)

什么是机器学习 K-Means聚类是一种无监督学习算法&#xff0c;用于将数据集分成K个不同的组&#xff08;簇&#xff09;&#xff0c;每个组内的数据点与组内其他点的相似度较高&#xff0c;而与其他组内的点相似度较低。这是通过迭代地调整簇中心和将数据点分配到最近的簇来实…

热压机PLC数据采集远程监控物联网解决方案

热压机PLC数据采集远程监控物联网解决方案 随着工业4.0时代的到来&#xff0c;智能制造已经成为制造业发展的重要方向。在热压机领域&#xff0c;PLC数据采集远程监控物联网解决方案为提高生产效率、降低维护成本、优化生产工艺提供了有效的手段。 一、热压机PLC数据采集远程…