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