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

本文探讨了在C++11中如何通过利用新特性来避免内存安全问题,如缓存区溢出和数据越界。重点介绍了std::array和智能指针在确保内存安全方面的应用,指出应避免直接使用指针操作,以减少潜在风险。通过示例和代码解释,展示了如何使用std::array进行安全的数组操作,并提到了memcpy_s在防止内存溢出中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

场景

  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. 何谓内存安全_什么是内存安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Peter(阿斯拉达)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值