[C/C++11]_[中级]_[如何编写内存安全的C++代码]

场景

  1. C/C++不是内存安全,比如指针操作内存时最常见的就是缓存区溢出,数据越界,使用未初始化的内存等。那么借助现代的C++新版本新特性能避免吗?

说明

  1. 目前我所知道的操作内存片段使用std::array,stringvector<uint8_t>来操作是可以达到内存安全的。不过需要使用它们的at()方法来操作,因为只有这个方法有范围检查,只要越界会抛出out_of_range的异常,达到内存安全的目的。注意,这里讲的都是对数组的动态索引,如果是常量,编译器一般能在编译期检测出越界错误,但是对实际项目来说没意义,因为索引或范围基本都是动态值。

  2. 要内存安全,那就编码时抛弃掉C数组操作。改为使用std::array,stringvector<uint8_t>来当数组用。

  3. std::array可以用于创建栈数组。 以下介绍常见的几种操作数据的内存安全操作。

      1. 初始化数组全部元素为0:
    array<char, 256> buf{}; 
    
      1. 访问指定索引,使用at方法,如果越界会抛出out_of_range异常。内存安全。
    
    buf.at(253) = 'a';
    buf.at(254) = 'b';
    cout << &buf.at(253) << endl;
    
    auto data = buf.data();
    auto a1 = (int)(int*)&buf.at(253);
    auto d1 = (int)(int*)(data + 253);
    
      1. 复制指定索引为起始地址的连续2个1字节数据。如果指定的末尾是buf.begin() + 257,会报超过ite末尾的断言错误(Debug模式)。
    array<char, 2> temp{};
    std::copy(&buf.at(253),&buf.at(255),temp.begin());
    cout << temp.at(0) << ":" << temp.at(1) << endl;
    
      1. 赋值2个字节的数据给原数组,不能使用iterator,因为iterator的相加没有进行边界检查,ite+257。还是得使用索引取址的方式,并使用C11memcpy_s函数来复制, 这个函数有内存溢出检查,比如count > destsz会报错。
    memcpy_s( void *restrict dest, rsize_t destsz,
                  const void *restrict src, rsize_t count );
    
    auto size = getOffset();
    cout << "size: " << size << endl;
    memcpy_s(&buf.at(254), buf.size() - 254, temp.data(), temp.size());
    
  4. 动态数组可以使用stringvector<uint8_t>来表示,它们都带了at()方法,并且可以取址获取实际数据的存储指针。

  5. 对于内存安全的空指针解引用, use after freeillegal free (of an already-freed pointer, or a non-malloced pointer), 目前知道可以通过共享指针避免。

    shared_ptr<string> str;
    if (str) //使用前需要进行非nullptr判断.
    	cout << str->c_str() << endl;
    
  6. 总结,编码时还是尽量避免使用指针吧。

例子

// test-array-modify.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>

#include <array>
#include <vector>
#include <assert.h>
#include <stdint.h>

using namespace std;

/**
* 1. 加这个是为了让编译器不能在编译器识别出常量。
*/
int getOffset() {
	string str;
	str.resize(256);
	return str.size();
}

void TestDynamicArray()
{
	cout << "============= TestDynamicArray ============" << endl;

	// -- 读取某个内存片段的值,全部初始化为0
	string buf;
	buf.resize(256);

	buf.at(253) = 'a';
	buf.at(254) = 'b';
	cout << &buf.at(253) << endl;

	auto data = buf.data();

	// -- 1. 访问指定索引,使用`at`方法,如果越界会抛出`out_of_range`异常。内存安全。
	auto a1 = (int)(int*)&buf.at(253);
	auto d1 = (int)(int*)(data + 253);
	cout << a1 << endl;
	cout << d1 << endl;

	assert(a1 == d1);

	cout << sizeof(short) << endl;
	// -- 2. 复制指定索引为起始地址的连续2个1字节数据。如果指定的末尾是buf.begin() + 257,会报超过ite末尾的断言错误(Debug模式)。内存安全
	array<char, 2> temp{};
	std::copy(&buf.at(253), &buf.at(255), temp.begin());
	cout << temp.at(0) << ":" << temp.at(1) << endl;

	// -- 3. 赋值2个字节的数据给原数组,如果temp.begin() + 255会报超过ite末尾的断言错误。
	auto size = getOffset();
	cout << "size: " << size << endl;
	memcpy_s(&buf.at(254), buf.size() - 254, temp.data(), temp.size());
	for (auto i = 253; i < buf.size(); ++i)
		cout << buf.at(i);

	cout << endl;
}

void TestDynamicArray2()
{
	cout << "============= TestDynamicArray2 ============" << endl;

	// -- 读取某个内存片段的值,全部初始化为0
	vector<uint8_t> buf;
	buf.resize(256);

	buf.at(253) = 'a';
	buf.at(254) = 'b';
	cout << &buf.at(253) << endl;

	auto data = buf.data();

	// -- 1. 访问指定索引,使用`at`方法,如果越界会抛出`out_of_range`异常。内存安全。
	auto a1 = (int)(int*)&buf.at(253);
	auto d1 = (int)(int*)(data + 253);
	cout << a1 << endl;
	cout << d1 << endl;

	assert(a1 == d1);

	cout << sizeof(short) << endl;
	// -- 2. 复制指定索引为起始地址的连续2个1字节数据。如果指定的末尾是buf.begin() + 257,会报超过ite末尾的断言错误(Debug模式)。内存安全
	array<char, 2> temp{};
	std::copy(&buf.at(253), &buf.at(255), temp.begin());
	cout << temp.at(0) << ":" << temp.at(1) << endl;

	// -- 3. 赋值2个字节的数据给原数组,如果temp.begin() + 255会报超过ite末尾的断言错误。
	auto size = getOffset();
	cout << "size: " << size << endl;
	memcpy_s(&buf.at(254), buf.size() - 254, temp.data(), temp.size());
	for (auto i = 253; i < buf.size(); ++i)
		cout << buf.at(i);

	cout << endl;
}

void TestFixArray()
{
	cout << "============= TestFixArray ============" << endl;
	// -- 读取某个内存片段的值,全部初始化为0
	array<char, 256> buf{};
	
	buf.at(253) = 'a';
	buf.at(254) = 'b';
	cout << &buf.at(253) << endl;
	
	auto data = buf.data();
	
	// -- 1. 访问指定索引,使用`at`方法,如果越界会抛出`out_of_range`异常。内存安全。
	auto a1 = (int)(int*)&buf.at(253);
	auto d1 = (int)(int*)(data + 253);
	cout << a1 << endl;
	cout << d1 << endl;

	assert(a1 == d1);

	cout << sizeof(short) << endl;
	// -- 2. 复制指定索引为起始地址的连续2个1字节数据。如果指定的末尾是buf.begin() + 257,会报超过ite末尾的断言错误(Debug模式)。内存安全
	array<char, 2> temp{};
	std::copy(&buf.at(253),&buf.at(255),temp.begin());
	cout << temp.at(0) << ":" << temp.at(1) << endl;

	// -- 3. 赋值2个字节的数据给原数组,如果temp.begin() + 255会报超过ite末尾的断言错误。
	auto size = getOffset();
	cout << "size: " << size << endl;
	memcpy_s(&buf.at(254), buf.size() - 254, temp.data(), temp.size());
	for (auto i = 253; i < buf.size(); ++i)
		cout << buf.at(i);

	cout << endl;
}

void TestNullPointer()
{
	cout << "============= TestNullPointer ============" << endl;
	shared_ptr<string> str;
	if (str) //使用前需要进行非nullptr判断.
		cout << str->c_str() << endl;

	//cout << str->c_str() << endl;
	//cout << str2->c_str() << endl; // Debug模式下是nullptr访问冲突; Release模式下是地址访问冲突,但是不一定会报错。
}


int main()
{
	TestFixArray();
	TestDynamicArray();
	TestDynamicArray2();
	TestNullPointer();

    std::cout << "Hello World!\n";
}

输出

============= TestFixArray ============
ab
8059381
8059381
2
a:b
size: 256
aab
============= TestDynamicArray ============
ab
14482421
14482421
2
a:b
size: 256
aab
============= TestDynamicArray2 ============
ab
14482421
14482421
2
a:b
size: 256
aab
============= TestNullPointer ============
Hello World!

参考

  1. std::array

  2. memcpy, memcpy_s

  3. 何谓内存安全_什么是内存安全

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

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

相关文章

关于stm32(CubeMX+HAL库)的掉电检测以及flash读写

1.掉电检测 CubeMX配置 只需使能PVD中断即可 但是使能了PVD中断后还需要自行配置一些PWR寄存器中的参数&#xff0c;我也通过HAL库进行编写 void PVD_config(void) {//配置PWRPWR_PVDTypeDef sConfigPVD; sConfigPVD.PVDLevel PWR_PVDLEVEL_7; …

蓝桥杯2023年第十四届省赛真题-与或异或

一共10个电门&#xff0c;穷举310次 #include<stdio.h> #include<math.h> int main(){int a[5][5], max (int)pow(3, 10), t, op, count 0;a[0][0] a[0][2] a[0][4] 1;a[0][1] a[0][3] 0;for(int k 0; k < max; k){t k;for(int i 1; i < 5; i){fo…

vite ssr服务端渲染

阅读 Vue文档 这一章里有说过&#xff0c;vue是支持服务端渲染的。 通过createSSRApp创建vue组件实例&#xff0c;并使用renderToString将在服务器渲染好template并返回字符串结构&#xff0c;通过替换占位字符将渲染好的字符串输出到html上&#xff0c;这样的一个过程就实现了…

发送短信验证码

​​​​​​【短信验证码-快速报备签名】三网短信接口-短信-短信验证码-短信服务-三网短信接口-短信-三网短信【最新版】_商业智能_电商_金融-云市场-阿里云阿里云云市场提供 专注企业短信服务10年运营与技术积累&#xff0c;稳定、安全、快速。服务&#xff0c;建站服务&…

【word技巧】word文件如何设置打开密码?可以试试这两种方法!

Word文件想要设置密码打开文件&#xff0c;我们可以给文件设置一个打开密码&#xff0c;这样只有知道密码的人才能够打开查看文件&#xff0c;今天分享两个word文件设置打开密码的方法。 一、 打开word文档后&#xff0c;点击【文件】-【信息】-【保护文档】这里有很多选项&a…

excel统计分析——一元直线回归

参考资料&#xff1a;生物统计学 两个具有因果关系的协变量如果呈直线关系&#xff0c;可以用直线回归模型来分析两个变量的关系。直线回归&#xff08;linear regression&#xff09;是回归分析中最简单的类型&#xff0c;建立直线回归方程并经检验证明两个变量存在直线回归关…

Altium Designer怎么设置默认原理图纸张大小

Altium Designer怎么设置默认原理图纸张大小 绘制原理图时我们需要设置好原理图图纸大小&#xff0c;建议大家可以将默认原理图图纸设置为A3&#xff0c;A3图纸大小可以容纳下大部分原理图&#xff0c;这样就不用每次画原理图前去修改图纸大小&#xff0c;可以提高设计效率。 …

Redis底层数据结构之Hash

文章目录 1. Redis底层hash编码格式2. Redis 6源码分析3. Redis 7源码分析 1. Redis底层hash编码格式 在redis6中hash的编码格式分别是ziplist&#xff08;压缩列表&#xff09;和hashtable&#xff0c;但在redis7中hash的编码格式变为了listpack&#xff08;紧凑列表&#xf…

如何不依赖Unity直接解压unitypackage的内容

使用场景 我们都知道unity的资源导出是导出成.unitypackage文件,如果要里面的内容,得打开Unity,将unitypackage导入进去才能看到里面的内容。 但是很多时候我们下了几十个unitypackage资源包,又不清楚好不好用,而且导入之后编译特别慢,unity又不提供批量解压的功能,所…

好消息!电商平台订单API同步订单详情信息免申请审核调用指南!

淘宝开放平台订单类API 测试key获取 拼多多开放平台订单API列表 custom-自定义API操作 taobao.custom/pinduoduo.custom 公共参数 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥api_nameString是API接口名称&…

day08-Mybatis入门

MyBatis 是一款优秀的 持久层 框架&#xff0c;用于简化 JDBC 的开发。 官网&#xff1a;https://mybatis.org/mybatis-3/zh/index.html 一、快速入门 1.1 Mybatis 操作数据库的步骤 准备工作(创建 springboot 工程、数据库表 user、实体类 User)引入 Mybatis 的相关依赖&…

基于Qt 和python 的自动升级功能

需求&#xff1a; 公司内部的一个客户端工具&#xff0c;想加上一个自动升级功能。 服务端&#xff1a; 1&#xff0c;服务端使用python3.7 &#xff0c;搭配 fastapi 和uvicorn 写一个简单的服务&#xff0c;开出一个get接口&#xff0c;用于客户端读取安装包的版本&#…

北京市办理大兴道路运输许可证所需条件及注意事项

尊敬的客户&#xff1a; 感谢您选择北京经典世纪集团有限公司作为您的信任合作伙伴。我们从多个角度&#xff0c;为您详细解析办理大兴道路运输许可证所需的条件及注意事项&#xff0c;以便您轻松高效地完成相关手续。&#xff08;游览器搜经典世纪胡云帅&#xff09;。 我们…

Android7.1 ANR error 弹窗处理

Android7.1 ANR error 弹窗处理 问题描述解决方法 郑重声明:本人原创博文&#xff0c;都是实战&#xff0c;均经过实际项目验证出货的 转载请标明出处:攻城狮2015 Platform: Rockchip OS:Android 7.1.2 Kernel: 3.10 问题描述 有时会用到第三方apk&#xff0c;内置到系统中&…

Linux从0到1——Linux环境基础开发工具的使用(上)

Linux从0到1——Linux环境基础开发工具的使用&#xff08;上&#xff09; 1. Linux软件包管理器yum1.1 yum介绍1.2 用yum来下载软件1.3 更新yum源 2. Linux编辑器&#xff1a;vi/vim2.1 vim的基本概念2.2 vim的基本操作2.3 vim正常模式命令集2.4 vim底行模式命令集2.5 视图模式…

【全志H616】-2 写一个自己的串口

【全志H616】-2 写一个自己的串口 1、基本命令 重启 sudo rebootLinux系统下一个文件夹的文件复制到另一个文件夹下 cp flags.c /home/user05/lab09/flags_revised.c //复制当前文件夹下的 flags.c 文件到 lab09 文件夹下flags_recised.c 文件cp oled_demo.c /home/orangep…

在图片上进行标记

文章目录 需求分析 需求 底图是一张图片&#xff0c;要在图上做标记&#xff0c;对标记的位置有交互行为鼠标滚顶页面&#xff0c;标记位置不发生变化页面发生缩放&#xff0c;标记位置不发生变化 分析 <template><divv-loading"loading"class"point-m…

什么是智慧公厕?对公共厕所智能实时监测管理控制,城市管理更高效智能

公共厕所一直以来都是城市管理的难题之一&#xff0c;但随着智慧科技的发展和应用&#xff0c;智慧公厕成为了解决这一问题的利器。智慧公厕是一种信息化的新型公共厕所&#xff0c;通过全面感知平台实时监测公共厕所的使用状态&#xff0c;并将数据转化为可视、可算、可管的数…

读取txt文件并统计每行最长的单词以及长度

读取txt文件并统计每行最长的单词以及长度 题目 在 D:\\documant.txt 文本中,文件中有若干行英文文本,每行英文文本中有若干个单词&#xff0c;每个单词不会跨行出现每行至多包含100个字符,要求编写一个程序,处理文件,分析各行中的单词,找到每行中的最长单词&#xff0c;分别…

互联网剧本杀小程序,如何创新发展提高收益

近年来&#xff0c;剧本杀深受年轻人的喜欢&#xff0c;一度成为了大众的社交方式&#xff0c;剧本杀为年轻人提供了一个全新的娱乐游戏和社交为一体的模式。 不过随着剧本杀市场入局的人越来越多&#xff0c;市场的发展也迎来了“拐点”&#xff0c;剧本杀逐渐趋向高质量发展…