您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“C++怎么對二進制數據進行處理與封裝”,內容詳細,步驟清晰,細節處理妥當,希望這篇“C++怎么對二進制數據進行處理與封裝”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
在電腦上一切數據都是通過二進制(0或1)進行存儲的,通過多位二進制數據可以進而表示整形、浮點型、字符、字符串等各種基礎類型數據或者一些更復雜的數據格式。
針對日常中一般的需求進行編程,我們通常無需關注底層的二進制數據。但如果要處理二進制文件(音頻、視頻、圖片等)、設計空間上更高效的數據結構(網絡數據幀、字節碼、protobuf)或者處理某些底層時,需要我們處理這些二進制數據。
計算機中,稱每一個二進制位為比特(bit,也稱:位),是計算機中的最小存儲單位。
每 8 比特組成一個字節(byte),一般是計算機實際存儲和處理的最小單位(可以是它的倍數),也就是說,計算機是以字節為最小單位分配空間或進行計算的,不能分配比字節更小的存儲空間(如,最小的數據類型是char,長度 1 字節,不支持申請 6 比特存儲空間)或者直接處理小于字節單位的數據(如,兩個 4 比特的數據相加減)。
若干字節構成一個計算機字(簡稱:字,word),表示計算機一次性處理事務的固定長度二進制數據,字的位數為字長。計算機是以字為單位處理或運算的,兩個常見的概念是CPU位數和操作系統位數。
CPU 的位數就是指 CPU 執行一次指令能處理的最大位數(一個字長),和 CPU 中的寄存器的位數對應。其中,地址寄存器 MAR 限制了計算機的尋址范圍,數據寄存器 MDR 限制了一次處理的數據長度。更多的位數帶來了更大的尋址空間和更強的運算能力。
說明:尋址范圍不等于內存大小,尋址對象有內存條、顯卡內存、聲卡、網卡和其他設備。之所以常把尋址范圍當作內存上限,是因為內存是CPU的主要尋址對象。
這里解釋一下常見的指令架構:x86 是 intel 推出的一種指令集架構(復雜指令集 CISC 架構),一開始只有32位的,叫 x86_32;后來 AMD 公司推出了兼容 x86_32 的 64 位指令集 amd64,被業界接受,intel 將其改名為 x86_64,簡稱 x64,而 x86_32 和 x86_64 可統稱為 x86。與 x86 相對的是基于精簡指令集RISC架構的 ARM 指令集架構,多用于移動設備。
操作系統基于 CPU 指令集實現,所以操作系統位數也直接對應 CPU 位數。由于 CPU 指令集的向下兼容性,所以 32 位操作系統也可以運行在 64 位的 CPU 上,但反過來不行。操作系統對軟件提供了向下兼容的能力,64 位的操作系統支持 64 和 32 位的程序,但 32 位的操作系統只支持 32 位的程序。
在大多語言中,最小的數據類型是 char,一個字節,二進制數據多用 unsigned char 表示,并寫作 uint8。語言底層常把它當作 int 進行運算。
二進制常數以“0b”開頭,如:0b001。二進制數據也常用8進制(以“0”開頭)和 16 進制(以“0x”開頭)表示,如:0257(175,八進制)、0x1f(31,16進制)。8 進制 1 個數字表示 3 位二進制數據,16 進制 1 個數字表示 4 位二進制數據,一個字節可以用 2 個 16 進制數表示。
若要處理小于一字節的數據,就要使用位運算符(&、|、^、~、>>、<<)。
位運算符 | 描述 | 運算規則 | 用途 |
---|---|---|---|
& | 與 | 兩個位都為1時,結果才為1 | 二進制位清零或得到指定位數據 |
| | 或 | 兩個位都為0時,結果才為0 | 二進制位設置為1;與對應位為0的數據相加 |
^ | 異或 | 兩個位相同為0,相異為1 | 反轉指定位 |
~ | 取反 | 0變1,1變0 | 二進制位全部取反 |
<< | 左移 | 各二進位全部左移若干位,高位丟棄,低位補0 | 求x∗2nx∗2n;將數據移到高位 |
>> | 右移 | 各二進位全部右移若干位,對無符號數,高位補0,有符號數,各編譯器處理方法不一樣,有的補符號位(算術右移),有的補0(邏輯右移) | 求x/2nx/2n;將數據移到低位 |
舉個例子,判斷某個字節的第3位是否是1:
// 先清0其他位,再判斷是否等于0b100 bool isOne = (byte & 0b100) == 0b100;
再舉個例子,計算機網絡 IP 協議中的 control flag 和 fragment offset 合起來存儲在 IP 頭部的第 7、8 字節,flag 占前三位,后 13 位為 fragment offset,可以通過以下運算獲得 flag 和 offset:
// 獲得flag要截取byte7前3位數據:先清空后5位,保留前3位數據,再右移5位將前3位數據移到起始 uint8_t flag = (byte7 & 0b11100000) >> 5; // 此處以大端存儲,獲得offset要截取byte7的低5位作為高位,byte8作為低位,求和:先清空byte7前3位,保留后5位數據,把它移到高8位上,再通過全0的低8位與byte8按位求或來求二者之和 ((byte7 & 0b00011111) << 8) | byte8;
補充說明,當需要多個字節表示一個數據類型時,需要定義數據的高位字節是存儲在高位地址空間還是低位地址空間,這就是大小端的定義。大端指高位字節存在低位地址,這是人的手寫習慣;小端指低位字節存高位地址。在處理用多個字節表示的數據時,首先要搞清楚數據是大端還是小端。
所以,我們可以基于上述知識寫一個無符號整形與字節流相互轉換的通用方法:
// true為大端,低位地址存高位字節 bool ENDIAN = true; /** * 將data轉換為無符號整形數字(無符號char,short,int,long,long long等) * @tparam T 目標類型,默認為 uint32_t * @param data 載荷數據 byte數組 * @param valueSize 數據長度,單位:byte,-1表示根據T類型自動計算 * @param default_value 默認值,默認為0 * @return 根據data轉換的無符號整形數據 */ template<typename T = uint32_t> T payloadToUnsignedInt(std::vector<uint8_t> data, int valueSize = -1, T default_value = uint32_t(0)) { if (valueSize == -1) valueSize = sizeof(T); if (valueSize > data.size()) return default_value; T value = 0; for (int i = 0; i < valueSize; i++) { if (ENDIAN) { value |= (data[i] & 0xff) << ((valueSize - 1 - i) << 3); } else { value |= (data[i] & 0xff) << (i << 3); } } return value; } /** * 無符號整形轉換為載荷 byte數組 * @param value 無符號整形數據 * @param valueSize 數據長度,單位:byte,-1表示根據T類型自動計算 * @return 載荷 byte數組 */ template<typename T> std::vector<uint8_t> uintToPayload(T value, int valueSize = -1) { if (valueSize == -1) valueSize = sizeof(T); std::vector<uint8_t> data(valueSize, 0); for (int i = 0; i < valueSize; i++) { if (ENDIAN) { data[i] = (value >> ((valueSize - 1 - i) << 3)) & 0xff; } else { data[i] = (value >> (i << 3)) & 0xff; } } return data; }
掌握了二進制數據的處理方法,接下來就是對二進制數據的封裝,將其封裝為人可以理解的對象。
二進制數據通常以 uint8_t 數組表示,不同位有不同的含義,需要根據實際含義進行解析后得到有意義的目標信息。所以重點就是描述每一位的含義,并基于該描述解析二進制數據,提供二進制數據與有含義的對象的相互轉換。
此處以自定義的二進制指令封裝為例進行說明(項目地址),但該配置項目適用于任意二進制數據封裝場景。面對這個需求,首先想到的是通過配置文件描述二進制流每一位的含義,加載配置文件后根據一些過濾條件配置確定當前二進制流段實際對應的配置并解析為字典。
由于項目包括一些嵌入式的內容,需要把所有文件編譯后燒入板子,不支持存儲普通文件格式的配置文件,所以采用變量形式的配置,全局聲明配置的類型信息和配置對象(cmd_manager),項目內任意位置定義該配置對象即可。在其他場景也可選擇 Json、xml 等配置格式。
本文設計的配置對象定義方式如下:
/** * 載荷配置項 */ const CmdManager cmd_manager = { 2, { // 指令個數,下面是每一個指令的配置 {"TCRQ", 3, { // 配置項名,配置項對應的字段數 {"TE_SEQ_NO", -1, &FT_SHORT, 0}, // 具體配置項內字段配置(字段名,字段偏移,字段類型,配置項該字段過濾條件 {"CMD", -1, &FT_CHARS_4, "TCRQ"}, // 配置項要求該字段等于"TCRQ",數據不滿足則不匹配該配置項 {"REPEAT_COUNT", -1, &FT_SHORT, 0}}} }};
項目會自動加載該配置對象,之后針對原始二進制數據通過 PayloadObjectMapFactory 工廠匹配對應配置并生成數據對象,可從數據對象獲得該對象類型(配置項名)并讀寫其中的字段值。或者指定配置項創建空的數據對象,進行數據設置后獲得其原始二進制數據載荷。
評價
該思路通過配置文件可以自由且動態的調整解析方式,易于復用、拓展或調整。其難點在于配置格式的設計,同時字典類型數據無法如直接聲明類型結構那樣清晰易用。
此處以計算機網絡數據幀封裝為例進行說明。c++ 底層對對象/結構體的成員字段采用類型對齊連續存儲方式,使用該特性可以基于實際含義自然聲明、使用字段,同時可以直接作為二進制數據流處理。實現示例如下:
/** * 數據抽象類,提供二進制流到對象的相互轉化能力 * 內部類,只復用代碼,不用于多態 * @tparam size 數據字節長度 */ template<int size> class DataType { public: DataType() { resetData(); } // 初始化所有數據 void resetData() const { memset((void *) (this), 0, size); } // 從二進制流加載數據 bool loadData(const std::vector<uint8_t>& data, int startIndex=0) { auto * p = (uint8_t *) this; // 將自身當作二進制數組處理 for (int i = 0; i < size; i++) { *p = data[i + startIndex]; p++; } return true; } // 基于自身生成新的二進制數據流 [[nodiscard]] std::vector<uint8_t> createData() const { std::vector<uint8_t> result; auto p = (uint8_t const *) this; for (int i = 0; i < size; i++) { result.push_back(*p); p++; } return result; } [[nodiscard]] int getSize() const { return size; } }; // 以順序聲明方式定義具體的二進制數據類型,支持嵌套聲明 class MACHeader : public DataType<14> { public: // 通過上述無符號整形與字節流相互轉化的方法將netType的讀寫進行封裝 [[nodiscard]] uint16_t getNetType() const { return payloadToUnsignedInt(std::vector<uint8_t>(netType.begin(), netType.end()), 2, uint16_t(0)); } void setNetType(uint16_t _netType) { auto data = uintToPayload(_netType, 2); std::copy(data.begin(), data.end(), netType.begin()); } // 提供與json互轉的能力,為了提供映射為python對象的能力 bool loadJson(const Json::Value& json); [[nodiscard]] Json::Value createJson() const; std::array<uint8_t, 6> desMac; // 占多個字節的數據采用std::array數組描述,可避免類型丟失,同時保證數據類型仍然一致對其 std::array<uint8_t, 6> srcMac; std::array<uint8_t, 2> netType; };
本項目還需要提供 c++ 的數據幀對象映射到 python 對象的能力,為了簡化 CPython 的拓展方法接口,c++ 層提供從 json 加載或生成 json 的能力,在 python 層實現一個 json 緩存,通過緩存提交和更新實現數據管理。
讀到這里,這篇“C++怎么對二進制數據進行處理與封裝”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。