欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C++實戰(zhàn)之二進(jìn)制數(shù)據(jù)處理與封裝

 更新時間:2022年08月04日 16:15:53   作者:_哲思  
在電腦上一切數(shù)據(jù)都是通過二進(jìn)制(0或1)進(jìn)行存儲的,通過多位二進(jìn)制數(shù)據(jù)可以進(jìn)而表示整形、浮點型、字符、字符串等各種基礎(chǔ)類型數(shù)據(jù)或者一些更復(fù)雜的數(shù)據(jù)格式。本文將為大家詳細(xì)講講二進(jìn)制數(shù)據(jù)處理與封裝,需要的可以參考一下

前言

最近在研究所做網(wǎng)絡(luò)終端測試的項目,包括一些嵌入式和底層數(shù)據(jù)幀的封裝調(diào)用。之前很少接觸對二進(jìn)制原始數(shù)據(jù)的處理與封裝,所以在此進(jìn)行整理。

以下例子主要以 c++ 語言進(jìn)行說明。

什么是二進(jìn)制數(shù)據(jù)

在電腦上一切數(shù)據(jù)都是通過二進(jìn)制(0或1)進(jìn)行存儲的,通過多位二進(jìn)制數(shù)據(jù)可以進(jìn)而表示整形、浮點型、字符、字符串等各種基礎(chǔ)類型數(shù)據(jù)或者一些更復(fù)雜的數(shù)據(jù)格式。

針對日常中一般的需求進(jìn)行編程,我們通常無需關(guān)注底層的二進(jìn)制數(shù)據(jù)。但如果要處理二進(jìn)制文件(音頻、視頻、圖片等)、設(shè)計空間上更高效的數(shù)據(jù)結(jié)構(gòu)(網(wǎng)絡(luò)數(shù)據(jù)幀、字節(jié)碼、protobuf)或者處理某些底層時,需要我們處理這些二進(jìn)制數(shù)據(jù)。

計算機中,稱每一個二進(jìn)制位為比特(bit,也稱:位),是計算機中的最小存儲單位。

每 8 比特組成一個字節(jié)(byte),一般是計算機實際存儲和處理的最小單位(可以是它的倍數(shù)),也就是說,計算機是以字節(jié)為最小單位分配空間或進(jìn)行計算的,不能分配比字節(jié)更小的存儲空間(如,最小的數(shù)據(jù)類型是char,長度 1 字節(jié),不支持申請 6 比特存儲空間)或者直接處理小于字節(jié)單位的數(shù)據(jù)(如,兩個 4 比特的數(shù)據(jù)相加減)。

若干字節(jié)構(gòu)成一個計算機字(簡稱:字,word),表示計算機一次性處理事務(wù)的固定長度二進(jìn)制數(shù)據(jù),字的位數(shù)為字長。計算機是以字為單位處理或運算的,兩個常見的概念是CPU位數(shù)操作系統(tǒng)位數(shù)。

CPU 的位數(shù)就是指 CPU 執(zhí)行一次指令能處理的最大位數(shù)(一個字長),和 CPU 中的寄存器的位數(shù)對應(yīng)。其中,地址寄存器 MAR 限制了計算機的尋址范圍,數(shù)據(jù)寄存器 MDR 限制了一次處理的數(shù)據(jù)長度。更多的位數(shù)帶來了更大的尋址空間和更強的運算能力。

說明:尋址范圍不等于內(nèi)存大小,尋址對象有內(nèi)存條、顯卡內(nèi)存、聲卡、網(wǎng)卡和其他設(shè)備。之所以常把尋址范圍當(dāng)作內(nèi)存上限,是因為內(nèi)存是CPU的主要尋址對象。

這里解釋一下常見的指令架構(gòu):x86 是 intel 推出的一種指令集架構(gòu)(復(fù)雜指令集 CISC 架構(gòu)),一開始只有32位的,叫 x86_32;后來 AMD 公司推出了兼容 x86_32 的 64 位指令集 amd64,被業(yè)界接受,intel 將其改名為 x86_64,簡稱 x64,而 x86_32 和 x86_64 可統(tǒng)稱為 x86。與 x86 相對的是基于精簡指令集RISC架構(gòu)的 ARM 指令集架構(gòu),多用于移動設(shè)備。

操作系統(tǒng)基于 CPU 指令集實現(xiàn),所以操作系統(tǒng)位數(shù)也直接對應(yīng) CPU 位數(shù)。由于 CPU 指令集的向下兼容性,所以 32 位操作系統(tǒng)也可以運行在 64 位的 CPU 上,但反過來不行。操作系統(tǒng)對軟件提供了向下兼容的能力,64 位的操作系統(tǒng)支持 64 和 32 位的程序,但 32 位的操作系統(tǒng)只支持 32 位的程序。

處理二進(jìn)制數(shù)據(jù)

在大多語言中,最小的數(shù)據(jù)類型是 char,一個字節(jié),二進(jìn)制數(shù)據(jù)多用 unsigned char 表示,并寫作 uint8。語言底層常把它當(dāng)作 int 進(jìn)行運算。

二進(jìn)制常數(shù)以“0b”開頭,如:0b001。二進(jìn)制數(shù)據(jù)也常用8進(jìn)制(以“0”開頭)和 16 進(jìn)制(以“0x”開頭)表示,如:0257(175,八進(jìn)制)、0x1f(31,16進(jìn)制)。8 進(jìn)制 1 個數(shù)字表示 3 位二進(jìn)制數(shù)據(jù),16 進(jìn)制 1 個數(shù)字表示 4 位二進(jìn)制數(shù)據(jù),一個字節(jié)可以用 2 個 16 進(jìn)制數(shù)表示。

若要處理小于一字節(jié)的數(shù)據(jù),就要使用位運算符(&、|、^、~、>>、<<)。

位運算符描述運算規(guī)則用途
&兩個位都為1時,結(jié)果才為1二進(jìn)制位清零或得到指定位數(shù)據(jù)
|兩個位都為0時,結(jié)果才為0二進(jìn)制位設(shè)置為1;與對應(yīng)位為0的數(shù)據(jù)相加
^異或兩個位相同為0,相異為1反轉(zhuǎn)指定位
~取反0變1,1變0二進(jìn)制位全部取反
<<左移各二進(jìn)位全部左移若干位,高位丟棄,低位補0求x∗2nx∗2n;將數(shù)據(jù)移到高位
>>右移各二進(jìn)位全部右移若干位,對無符號數(shù),高位補0,有符號數(shù),各編譯器處理方法不一樣,有的補符號位(算術(shù)右移),有的補0(邏輯右移)求x/2nx/2n;將數(shù)據(jù)移到低位

舉個例子,判斷某個字節(jié)的第3位是否是1:

// 先清0其他位,再判斷是否等于0b100
bool isOne = (byte & 0b100) == 0b100;

再舉個例子,計算機網(wǎng)絡(luò) IP 協(xié)議中的 control flag 和 fragment offset 合起來存儲在 IP 頭部的第 7、8 字節(jié),flag 占前三位,后 13 位為 fragment offset,可以通過以下運算獲得 flag 和 offset:

// 獲得flag要截取byte7前3位數(shù)據(jù):先清空后5位,保留前3位數(shù)據(jù),再右移5位將前3位數(shù)據(jù)移到起始
uint8_t flag = (byte7 & 0b11100000) >> 5;
// 此處以大端存儲,獲得offset要截取byte7的低5位作為高位,byte8作為低位,求和:先清空byte7前3位,保留后5位數(shù)據(jù),把它移到高8位上,再通過全0的低8位與byte8按位求或來求二者之和
((byte7 & 0b00011111) << 8) | byte8;

補充說明,當(dāng)需要多個字節(jié)表示一個數(shù)據(jù)類型時,需要定義數(shù)據(jù)的高位字節(jié)是存儲在高位地址空間還是低位地址空間,這就是大小端的定義。大端指高位字節(jié)存在低位地址,這是人的手寫習(xí)慣;小端指低位字節(jié)存高位地址。在處理用多個字節(jié)表示的數(shù)據(jù)時,首先要搞清楚數(shù)據(jù)是大端還是小端。

所以,我們可以基于上述知識寫一個無符號整形與字節(jié)流相互轉(zhuǎn)換的通用方法:

// true為大端,低位地址存高位字節(jié)
bool ENDIAN = true;
 
/**
 * 將data轉(zhuǎn)換為無符號整形數(shù)字(無符號char,short,int,long,long long等)
 * @tparam T 目標(biāo)類型,默認(rèn)為 uint32_t
 * @param data 載荷數(shù)據(jù) byte數(shù)組
 * @param valueSize 數(shù)據(jù)長度,單位:byte,-1表示根據(jù)T類型自動計算
 * @param default_value 默認(rèn)值,默認(rèn)為0
 * @return 根據(jù)data轉(zhuǎn)換的無符號整形數(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;
}
 
/**
 * 無符號整形轉(zhuǎn)換為載荷 byte數(shù)組
 * @param value 無符號整形數(shù)據(jù)
 * @param valueSize 數(shù)據(jù)長度,單位:byte,-1表示根據(jù)T類型自動計算
 * @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ù)

掌握了二進(jìn)制數(shù)據(jù)的處理方法,接下來就是對二進(jìn)制數(shù)據(jù)的封裝,將其封裝為人可以理解的對象。

二進(jìn)制數(shù)據(jù)通常以 uint8_t 數(shù)組表示,不同位有不同的含義,需要根據(jù)實際含義進(jìn)行解析后得到有意義的目標(biāo)信息。所以重點就是描述每一位的含義,并基于該描述解析二進(jìn)制數(shù)據(jù),提供二進(jìn)制數(shù)據(jù)與有含義的對象的相互轉(zhuǎn)換。

思路1:基于配置文件

此處以自定義的二進(jìn)制指令封裝為例進(jìn)行說明(項目地址),但該配置項目適用于任意二進(jìn)制數(shù)據(jù)封裝場景。面對這個需求,首先想到的是通過配置文件描述二進(jìn)制流每一位的含義,加載配置文件后根據(jù)一些過濾條件配置確定當(dāng)前二進(jìn)制流段實際對應(yīng)的配置并解析為字典。

由于項目包括一些嵌入式的內(nèi)容,需要把所有文件編譯后燒入板子,不支持存儲普通文件格式的配置文件,所以采用變量形式的配置,全局聲明配置的類型信息和配置對象(cmd_manager),項目內(nèi)任意位置定義該配置對象即可。在其他場景也可選擇 Json、xml 等配置格式。

本文設(shè)計的配置對象定義方式如下:

/**
 * 載荷配置項
 */
const CmdManager cmd_manager = { 2, {  // 指令個數(shù),下面是每一個指令的配置
        {"TCRQ", 3, {  // 配置項名,配置項對應(yīng)的字段數(shù)
            {"TE_SEQ_NO", -1, &FT_SHORT, 0},  // 具體配置項內(nèi)字段配置(字段名,字段偏移,字段類型,配置項該字段過濾條件
            {"CMD", -1, &FT_CHARS_4, "TCRQ"},  // 配置項要求該字段等于"TCRQ",數(shù)據(jù)不滿足則不匹配該配置項
            {"REPEAT_COUNT", -1, &FT_SHORT, 0}}}
}};

項目會自動加載該配置對象,之后針對原始二進(jìn)制數(shù)據(jù)通過 PayloadObjectMapFactory 工廠匹配對應(yīng)配置并生成數(shù)據(jù)對象,可從數(shù)據(jù)對象獲得該對象類型(配置項名)并讀寫其中的字段值。或者指定配置項創(chuàng)建空的數(shù)據(jù)對象,進(jìn)行數(shù)據(jù)設(shè)置后獲得其原始二進(jìn)制數(shù)據(jù)載荷。

評價

該思路通過配置文件可以自由且動態(tài)的調(diào)整解析方式,易于復(fù)用、拓展或調(diào)整。其難點在于配置格式的設(shè)計,同時字典類型數(shù)據(jù)無法如直接聲明類型結(jié)構(gòu)那樣清晰易用。

思路2:基于數(shù)據(jù)底層存儲方式

此處以計算機網(wǎng)絡(luò)數(shù)據(jù)幀封裝為例進(jìn)行說明。c++ 底層對對象/結(jié)構(gòu)體的成員字段采用類型對齊連續(xù)存儲方式,使用該特性可以基于實際含義自然聲明、使用字段,同時可以直接作為二進(jìn)制數(shù)據(jù)流處理。實現(xiàn)示例如下:

/**
 * 數(shù)據(jù)抽象類,提供二進(jìn)制流到對象的相互轉(zhuǎn)化能力
 * 內(nèi)部類,只復(fù)用代碼,不用于多態(tài)
 * @tparam size 數(shù)據(jù)字節(jié)長度
 */
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:
    // 通過上述無符號整形與字節(jié)流相互轉(zhuǎn)化的方法將netType的讀寫進(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對象的能力
    bool loadJson(const Json::Value& json);
    [[nodiscard]] Json::Value createJson() const;
 
    std::array<uint8_t, 6> desMac;  // 占多個字節(jié)的數(shù)據(jù)采用std::array數(shù)組描述,可避免類型丟失,同時保證數(shù)據(jù)類型仍然一致對其
    std::array<uint8_t, 6> srcMac;
    std::array<uint8_t, 2> netType;
};

本項目還需要提供 c++ 的數(shù)據(jù)幀對象映射到 python 對象的能力,為了簡化 CPython 的拓展方法接口,c++ 層提供從 json 加載或生成 json 的能力,在 python 層實現(xiàn)一個 json 緩存,通過緩存提交和更新實現(xiàn)數(shù)據(jù)管理。為了致敬git,項目實際提交和更新方法命名為 push 和 pull,(╯▔^▔)╯。

評價

該思路通過一種類似順序聲明的方式(有點像配置)定義數(shù)據(jù)流每個位置的實際含義,使用時清晰直接,并巧妙的通過其底層原理便捷的在對象和二進(jìn)制數(shù)據(jù)流之間提供轉(zhuǎn)化操作。但由于其需要實際聲明類型,不如思路1動態(tài)靈活易復(fù)用。

到此這篇關(guān)于C++實戰(zhàn)之二進(jìn)制數(shù)據(jù)處理與封裝的文章就介紹到這了,更多相關(guān)C++二進(jìn)制數(shù)據(jù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++實現(xiàn)LeetCode(52.N皇后問題之二)

    C++實現(xiàn)LeetCode(52.N皇后問題之二)

    這篇文章主要介紹了C++實現(xiàn)LeetCode(52.N皇后問題之二),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-07-07
  • C/C++實現(xiàn)的MD5哈希校驗的示例代碼

    C/C++實現(xiàn)的MD5哈希校驗的示例代碼

    MD5算法是一種廣泛使用的 Hash 算法,常用于確保信息傳輸?shù)耐暾耘c一致性,本文主要介紹了C/C++實現(xiàn)的MD5哈希校驗的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2023-10-10
  • C/C++實現(xiàn)圖形學(xué)掃描線填充算法

    C/C++實現(xiàn)圖形學(xué)掃描線填充算法

    這篇文章主要介紹了C/C++實現(xiàn)圖形學(xué)掃描線填充算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • C++中g(shù)etline()、gets()等函數(shù)的用法詳解

    C++中g(shù)etline()、gets()等函數(shù)的用法詳解

    這篇文章主要介紹了C++中g(shù)etline()、gets()等函數(shù)的用法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-02-02
  • C++中的策略模式淺析

    C++中的策略模式淺析

    策略模式屬于C++設(shè)計模式中行為模式之一,該模式定義了一系列算法,并將每個算法封裝起來,使它們可以相互替換。本文將通過示例詳細(xì)講解這一模式,需要的可以參考一下
    2023-02-02
  • C語言編程中統(tǒng)計輸入的行數(shù)以及單詞個數(shù)的方法

    C語言編程中統(tǒng)計輸入的行數(shù)以及單詞個數(shù)的方法

    這篇文章主要介紹了C語言編程中統(tǒng)計輸入的行數(shù)以及單詞個數(shù)的方法,利用最基礎(chǔ)的循環(huán)和判斷語句寫成,需要的朋友可以參考下
    2015-11-11
  • C++模擬實現(xiàn)list功能

    C++模擬實現(xiàn)list功能

    list的底層是一個循環(huán)雙向鏈表結(jié)構(gòu),雙向鏈表中每個元素存儲在互不相關(guān)的獨立節(jié)點中,在節(jié)點中通過指針指向其前一個元素和后一個元素,接下來通過本文給大家分享C++模擬實現(xiàn)list的示例代碼,需要的朋友可以參考下
    2021-08-08
  • C語言實現(xiàn)簡易的掃雷游戲

    C語言實現(xiàn)簡易的掃雷游戲

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)簡易的掃雷游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • c++下使用windows api遍歷指定文件夾及其子文件夾中的文件

    c++下使用windows api遍歷指定文件夾及其子文件夾中的文件

    這篇文章主要介紹了c++下使用windows api遍歷指定文件夾及其子文件夾中的文件實現(xiàn)代碼,一般都是通過c++自帶的函數(shù)實現(xiàn)
    2021-07-07
  • C++設(shè)計模式之命令模式

    C++設(shè)計模式之命令模式

    這篇文章主要介紹了C++設(shè)計模式之命令模式,本文講解了什么是命令模式、命令模式的使用場合等內(nèi)容,并給出了一個代碼實例,需要的朋友可以參考下
    2014-10-10

最新評論