[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
英文地址,請注明出處:http://en.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;并推送到…