场景
C/C++
不是内存安全,比如指针操作内存时最常见的就是缓存区溢出,数据越界,使用未初始化的内存等。那么借助现代的C++
新版本新特性能避免吗?
说明
-
目前我所知道的操作内存片段使用
std::array
,string
或vector<uint8_t>
来操作是可以达到内存安全的。不过需要使用它们的at()
方法来操作,因为只有这个方法有范围检查,只要越界会抛出out_of_range
的异常,达到内存安全的目的。注意,这里讲的都是对数组的动态索引,如果是常量,编译器一般能在编译期检测出越界错误,但是对实际项目来说没意义,因为索引或范围基本都是动态值。 -
要内存安全,那就编码时抛弃掉
C数组
操作。改为使用std::array
,string
或vector<uint8_t>
来当数组用。 -
std::array
可以用于创建栈数组。 以下介绍常见的几种操作数据的内存安全操作。-
- 初始化数组全部元素为
0
:
- 初始化数组全部元素为
array<char, 256> buf{};
-
- 访问指定索引,使用
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);
-
- 复制指定索引为起始地址的连续2个1字节数据。如果指定的末尾是
buf.begin() + 257
,会报超过ite末尾的断言错误(Debug
模式)。
- 复制指定索引为起始地址的连续2个1字节数据。如果指定的末尾是
array<char, 2> temp{}; std::copy(&buf.at(253),&buf.at(255),temp.begin()); cout << temp.at(0) << ":" << temp.at(1) << endl;
-
- 赋值
2
个字节的数据给原数组,不能使用iterator
,因为iterator
的相加没有进行边界检查,ite+257
。还是得使用索引取址的方式,并使用C11
的memcpy_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());
-
-
动态数组可以使用
string
或vector<uint8_t>
来表示,它们都带了at()
方法,并且可以取址获取实际数据的存储指针。 -
对于内存安全的空指针解引用,
use after free
,illegal free (of an already-freed pointer, or a non-malloced pointer)
, 目前知道可以通过共享指针避免。shared_ptr<string> str; if (str) //使用前需要进行非nullptr判断. cout << str->c_str() << endl;
-
总结,编码时还是尽量避免使用指针吧。
例子
// 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!
参考
-
std::array
-
memcpy, memcpy_s
-
何谓内存安全_什么是内存安全