[C/C++内存安全]_[中级]_[如何避免数组访问越界]

场景

  1. C/C++的标准在C++26以前还没支持内存安全的访问连续内存的类或特性。在开发分析内存数据或文件数据的程序时,经常需要把一段内存数据复制到另一个堆空间里。 这时目标内存空间由于起始地址的移动,剩余大小的计算错误,经常会导致访问越界错误。关键是C/C++的访问越界错误的行为是未定义的。 未定义的错误也不会立马导致程序崩溃,而是可能程序运行一段时间,某次访问越界操作访问了受保护的页面才会导致崩溃。那么,C/C++有办法防止这类访问越界操作吗?

说明

  1. 目前C++11的类std::array静态数组可以使用at()方法或者[]操作符来访问指定索引的内容,如果下标超过数组的长度会抛出std::out_of_range异常。 也可以通过data()访问连续的内存,但是如果通过这种指针const T*越界访问时,不会抛出异常,是未定义行为。
array<uint8_t, 3> arr1{}; // 初始化为0
arr1[4] = 0; // 运行时抛出out_of_range异常
auto p1 = arr1.data();
  1. C++11的动态数组可以使用std::vector<uint8_t>std::string来代替malloc()函数。这两个类都是支持at()方法和下标[]操作符,同样也是支持访问越界抛异常。 也支持data()方法访问连续内存,这种也是访问越界时,未定义行为,不会抛出异常。
vector<uint8_t> as;
auto asData = as.data();string data(8, 0);
cout << data.size() << endl;string buf(3, 0);
buf.at(0) = 'a';
buf.at(1) = 'b';
buf.at(2) = 'c';auto myData = buf.data();
  1. 对于复制内存数据的函数,也就只有两个函数std::copymemcpy_s相对安全的函数。
  • std::copy在算法库<algorithm>里。它的作用是复制两个源枚举区间的数据到目标枚举。如果源枚举大小大于目标枚举所能容纳的大小,那么会在Debug模式时报断言错误cannot seek array iterator after end。缺点是Release模式并不会报错,而且不能设置目标枚举的长度。
     array<uint8_t, 3> arr1{}; // 初始化为0string buf(3, 0);buf.at(0) = 'a';buf.at(1) = 'b';buf.at(2) = 'c';auto myData = buf.data();// Debug运行时抛出"cannot seek array iterator after end"断言错误。std::copy(buf.begin(), buf.end(), arr1.begin() + 1);
    
  • memcpy_sC11添加的函数,多出了一个目标缓存的长度的参数。 但是这个函数对于源长度和目标长度实际上是否<=源缓存和目标缓存里有足够的长度并没有判断。即加入传错了大于实际长度的destszcount参数只会产生越界的未定义行为。
    void* memcpy( void *dest, const void *src, size_t count );(until C99)
    errno_t memcpy_s( void *restrict dest, rsize_t destsz,const void *restrict src, rsize_t count );(since C11)
    
  1. 看完上边的说明,可以发现在上C++11上对数组越界行为并没有严格的保护,这样这些类和函数的安全性就降低很多,需要程序员自己花精力去计算数组长度。 实际上,如果一个数组能做好这两方面,就不会出现数组越界问题。一方面避免使用指针操作,使用方法和索引访问指定位置的内容;另一方面是对源数组和目标数组的传入长度进行越界判断后再进行复制操作。以下实现了一个安全数组SafeArray,可以避免数组访问越界问题。 使用它的内部复制方法,能记录已使用的数组空间和剩余的长度,避免越界。安全数组的目标是不会产生未定义的越界访问行为。
  • reset方法来重置已使用索引index_
  • copy方法可以判断destSize长度,当然传入的源sourceSize也是得使用SafeArray来管理可使用长度才不会越界。
  • begintotal方法可以获取数组的起始地址和长度。
  • currentremain是当前可用数组地址和剩余长度。

例子

#include <iostream>#include <array>
#include <assert.h>
#include <vector>
#include <string>
#include <functional>
#include <stdint.h>using namespace std;class SafeArray
{
public:SafeArray(int size) {buf_ = (uint8_t*)malloc(size+1);if (buf_) {memset(buf_, 0, size+1);size_ = size;}else {throw "Error allocate size memory.";}}~SafeArray() {free(buf_);}public:uint8_t* begin() {return buf_;}int total() {return size_;}void clear() {memset(buf_, 0, size_);}public:uint8_t& at(int index) {if (index < size_)return *(buf_ + index);string message("Index exceeds maximum limit!");message.append(" -> ").append(to_string(index));throw std::out_of_range(message);}uint8_t& operator [](int index) {return at(index);}operator uint8_t*(){return current();}uint8_t* current() {if (index_ >= size_)return NULL;return buf_ + index_;}int remain() {return size_ - index_;}int remain(int maxSize) {return min(remain(), maxSize);}void reset() {index_ = 0;}uint8_t* add(int number) {if ((index_ + number) < 0)return NULL;if ((index_ + number) > size_)return NULL;index_ += number;return buf_ + index_;}bool full() {return (index_ + 1) == size_;}int copy(uint8_t* dest, int destSize, uint8_t* source, int sourceSize) {if (!destSize || !sourceSize)return 0;auto lSize = remain();if (!lSize)return 0;if (destSize > lSize)destSize = lSize;if (sourceSize > destSize)sourceSize = destSize;if (memcpy_s(dest, destSize, source, sourceSize) == 0) {auto count = min(destSize, sourceSize);return (add(count))?count:0;}return 0;}int index() {return index_;}const char* c_str() {if (index_ == 0)return "";return (const char*)buf_;}private:uint8_t *buf_ = NULL;int size_ = 0;int index_ = 0;
};void TestDynamicArray()
{array<uint8_t, 3> arr1{}; // 初始化为0//arr1[4] = 0; // 运行时抛出异常auto p1 = arr1.data();vector<uint8_t> as;auto asData = as.data();string data(8, 0);cout << data.size() << endl;string buf(3, 0);buf.at(0) = 'a';buf.at(1) = 'b';buf.at(2) = 'c';auto myData = buf.data();// Debug运行时抛出"cannot seek array iterator after end"断言错误。// std::copy(buf.begin(), buf.end(), arr1.begin() + 1);// 非安全方式1: 复制内存数据到动态数组,如果越界,会抛出out_of_range异常。memcpy_s(&data.at(0),3,buf.data(),buf.size());cout << data.c_str() << endl;// 安全方式auto sPos = 3;auto sizeSouce = &buf.at(sPos - 1) - buf.data() + 1;memcpy_s(&data.at(sPos),data.size() - sPos,buf.data(),sizeSouce);cout << data.c_str() << endl;}void TestDynamicArray2()
{SafeArray data(8);cout << data.total() << endl;SafeArray buf(3);buf.at(0) = 'a';buf.at(1) = 'b';buf.at(2) = 'c';// 1. 安全方式data.copy(data, data.remain(), buf, buf.total());cout << data.c_str() << endl;data[3] = 'd';cout << data.c_str() << endl;// 2. 继续复制,用完剩余空间data.copy(data, data.remain(), buf, 3);cout << data.c_str() << endl;data.copy(data, data.remain(), buf, 3);cout << data.c_str() << endl;// 3. 目标空间已满,不会再复制。data.copy(data, data.remain(), buf, 3);cout << data.c_str() << endl;// 4. 直接指定目标剩余空间超出最大容量,超过剩余大小,会使用剩余大小代替指定容量。data.copy(data, 100, buf, 3);cout << data.c_str() << endl;// 5. 重置缓存,重新使用; 目标长度如果超出剩余长度,会只使用剩余长度。data.reset();data.clear();data.copy(data, 100, buf, 3);cout << data.c_str() << endl;// 6. 越界访问,会抛出异常try {data[100] = 'A';}catch (const std::out_of_range& e) {std::cerr << "Error: " << e.what() << std::endl;}}int main()
{std::cout << "Hello World!\n";TestDynamicArray();TestDynamicArray2();
}

输出

Hello World!
8
abc
abcabc
8
abc
abcd
abcabc
abcabcab
abcabcab
abcabcab
abc
Error: Index exceeds maximum limit! -> 100

参考

  1. std::array

  2. memcpy, memcpy_s

  3. 如何编写内存安全的C++代码

  4. std::copy, std::copy_if

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

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

相关文章

rabbitmq 与 Erlang 的版本对照表 win10 安装方法

win10 64位系统 安装的版本 otp_win64_27.3.3.exe rabbitmq-server-4.1.1.exe rabbitmq 与 Erlang 的版本对照表 Erlang Version Requirements This guide covers Erlang/OTP version requirements https://www.rabbitmq.com/docs/which-erlang Erlang 28 is not currently…

kali安装教程

kali教程 我下载的是kali的集成环境&#xff0c;可以直接进行打开&#xff0c;无需进行安装。 Get Kali | Kali Linux&#xff0c; 官网下载路径 直接按enter键 安装完成 生成一个小皮安装链接 会给你生成一个外网和内网地址&#xff0c; 可以进行浏览 点击我同意这个协议…

微信小程序入门实例_____快速搭建一个快递查询小程序​

&#x1f337;&#x1f337;之前几篇博文我们一起开发了天气查询、单词速记和待办事项小程序&#xff0c;这次我们来对生活中常用的功能 —— 快递查询来探索相关的小程序。网购已经成为大家生活的一部分&#xff0c;有了自己的快递查询小程序&#xff0c;不用切换多个应用&…

【防火墙基础之传统墙到 UTM 到 NGFW 再到 AI 的变化】

防火墙技术演进与未来趋势&#xff1a;从传统防御到AI驱动的智能安全 防火墙技术历经数十年发展&#xff0c;已从早期的简单包过滤演进为融合AI的智能安全平台。当前&#xff0c;传统爬虫防护技术如频率限制和人机校验已无法应对现代攻击&#xff0c;而全面风控体系通过多维协同…

【仿muduo库实现并发服务器】Poller模块

仿muduo库实现并发服务器 1.Poller模块成员变量创建epoll模型对于一个描述符添加或修改事件监控对于一个描述符移除事件监控启动epoll事件监控&#xff0c;获取所有活跃连接 1.Poller模块 Poller模块主要是对任意的描述符进行IO事件监控。 它是对epoll的封装&#xff0c;可以让…

小程序学习笔记:使用 MobX 实现全局数据共享,实例创建、计算属性与 Actions 方法

在小程序开发过程中&#xff0c;组件间的数据共享是一个常见且关键的问题。今天&#xff0c;我们就来深入探讨一下如何在小程序中实现全局数据共享&#xff0c;借助 MobX 相关的包&#xff0c;让数据管理变得更加高效便捷。 什么是全局数据共享 全局数据共享&#xff0c;也被…

观测云 × AWS SSO:权限治理可观测实践

AWS IAM Identity Center 介绍 AWS IAM Identity Center&#xff08;原 AWS Single Sign-On&#xff09;是 AWS 提供的一项云原生身份与访问管理&#xff08;IAM&#xff09;服务&#xff0c;旨在集中简化多 AWS 账户、多业务应用的安全访问控制。 观测云 观测云是一款专为 …

springboot整合配置swagger3

一. swagger3介绍 Swagger 3 是基于 OpenAPI 规范 3.0 的 API 文档工具&#xff0c;用于设计、构建和消费 RESTful API。它通过标准化描述 API 的接口、参数、响应等元数据&#xff0c;实现以下核心功能&#xff1a; 自动生成交互式文档API 测试与调试代码生成&#xff08;客…

RabbitMQ 4.1.1初体验

为什么选择 RabbitMQ&#xff1f;* RabbitMQ 是一款可靠且成熟的消息代理和流处理中间件&#xff0c;可轻松部署在云端、本地数据中心或您的开发机上&#xff0c;目前已被全球数百万用户使用。 优势在哪里 互操作性 RabbitMQ 支持多种开放标准协议&#xff0c;包括 AMQP 1.0 和…

【精华】QPS限流等场景,Redis其他数据结构优劣势对比

下面是一个详细的 Redis 数据结构对比表&#xff0c;比较它们在实现 QPS 限流 / 滑动窗口统计 / 查定比监控等场景中的适用性&#xff1a; ✅ Redis 数据结构对比表&#xff08;用于接口限流 / QPS 监控&#xff09; 维度String INCR 固定窗口List 滑动窗口Hash 计数器ZSet 滑…

顶层设计:支持单元化、灰度化的应用架构

一、顶层目标 业务连续性&#xff1a;任何单元故障不影响整体弹性伸缩&#xff1a;根据业务流量横向扩展灵活灰度&#xff1a;任何发布都可逐步平滑上线成本可控&#xff1a;单元化带来的资源冗余最小 二、核心理念 设计目标核心理念单元化垂直拆分&#xff0c;分而治之&…

MacOS Safari 如何打开F12 开发者工具 Developer Tools

背景 If you’re a web develper, the Safari Develop menu provides tools you can use to make sure your website works well with all standards-based web browsers. 解决 If you don’t see the Develop menu in menu bar, Choose Safari > settingsClick Advanced…

2025—暑期训练一

A 本题描述了一个最优路径规划问题的解法&#xff0c;核心思路是利用数轴上区间覆盖的特性&#xff0c;将问题简化为两个端点的访问问题。以下是关键点的详细解析&#xff1a; 核心观察 区间覆盖特性 给定的位置数组 x1, x2, ..., xn 是严格递增的&#xff08;即 x1 < x2 …

ubuntu 18.04配置镜像源

配置镜像源的主要作用是优化软件下载速度、提升系统更新稳定性&#xff0c;并确保软件包获取的可靠性 我这里配置阿里云镜像源 镜像的具体内容参考此文: 文章链接 以防万一,先备份一下 sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak然后开始修改 sudo nano /etc…

RecyclerView中跳转到最后一条item并确保它在可视区域内显示

在RecyclerView中跳转并显示最后一条Item 要在RecyclerView中跳转到最后一条item并确保它在可视区域内显示&#xff0c;可以使用以下几种方法&#xff1a; 1. 使用scrollToPosition()方法&#xff08;基本方法&#xff09; recyclerView.scrollToPosition(adapter.getItemCo…

ubuntu22 桌面版开启root登陆

一、先创建root sudo passwd root 二、注释代码 vim /etc/pam.d/gdm-password vim/etc/pam.d/gdm-autologin 都注释 auth required pam_succeed_if.so user ! root quiet_success 三、修改profile文件 vim /root/.profile 注释掉 mesg n 2&#xff1e; /dev/null || true 插入新…

docker学习二天之镜像操作与容器操作

镜像的一般运用过程 一、镜像&#xff08;Image&#xff09;操作 镜像是容器的基础模板&#xff0c;存储在本地或远程仓库中。 1. 镜像拉取 # 从指定镜像源拉取 docker pull docker.m.daocloud.io/library/nginx 2. 镜像查看 # 列出本地镜像 docker images # 或 docker image…

多个参数用websocket 向io 服务器发送变量,一次发一个,并接收响应

问题&#xff1a;多个参数用websocket 向io 服务器发送变量&#xff0c;一次发一个&#xff0c;并接收响应&#xff0c;如果是多个变量&#xff0c;但还是需要一个个发送&#xff0c;应该怎么实现&#xff0c;思路是什么样子的呢&#xff1f;用数组的话&#xff0c;应该怎么用&…

Flink-05学习 接上节,将FlinkJedisPoolConfig 从Kafka写入Redis

上节成功实现了FlinkKafkaConsumer消费Kafka数据&#xff0c;并将数据写入到控制台&#xff0c;接下来将继续将计算的结果输入到redis中。 pom.xml 引入redis到pom包 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://mave…

git教程-pycharm使用tag打标签

一.生成tag标签 前言 当我们的代码完成了第一阶段的需求&#xff0c;版本稳定后&#xff0c;希望能出个稳定版本。于是在 commit 后需要打个 tag 标签&#xff0c;也就是我们平常说的版本号&#xff0c;如v1.0版本 本篇讲解如何使用 pycharm 打 tag 标签&#xff0c;并推送到…