簡體   English   中英

Boolean 位域與邏輯位掩碼或位移 - C++

[英]Boolean bit fields vs logical bit masking or bit shifting - C++

我有一系列的課程需要 boolean 個字段,大約在 4-10 個之間。 我不想為每個 boolean 使用一個字節。我一直在研究位域結構,例如:

struct BooleanBitFields
    {
        bool b1:1;
        bool b2:1;
        bool b3:1;
        bool b4:1;
        bool b5:1;
        bool b6:1;
    };

但是在做了一些研究之后,我看到很多人說這會導致 memory 訪問效率低下並且不值得節省 memory。 我想知道這種情況的最佳方法是什么。 我應該使用位字段,還是使用帶有位掩碼(and's and or s)的字符來存儲 8 位? 如果第二種解決方案是位移位還是使用邏輯更好?

如果有人可以評論他們會使用什么方法以及為什么它會真正幫助我決定我應該下哪條路線 go。

提前致謝!

由於桌面盒上有大的地址空間,32/64位布爾值的陣列可能看起來很浪費,事實上確實如此,但大多數開發人員並不關心,(包括我)。 在RAM限制的嵌入式控制器上,或者在訪問驅動程序中的硬件時,請確保使用位域,否則..

除了R / W簡易/速度之外,另一個問題是32位或64位布爾值比中間的一位線程更安全,必須由多個邏輯運算來操作。

位字段僅是編譯器的推薦。 編譯器可以隨意實現它們。 在嵌入式系統上,有一些編譯器可以保證1位到位的映射。 其他編譯器沒有。

我會使用常規結構,就像你的結果,但沒有位字段。 使它們成為無符號字符 - 最短的數據類型。 如果您的IDE支持自動完成,則結構將使編輯時更容易訪問它們。

使用int位數組(留下很多空間來擴展,並且單個char沒有優勢)並使用掩碼常量進行測試:

#define BOOL_A 1
#define BOOL_B 1 << 1
#define BOOL_C 1 << 2
#define BOOL_D 1 << 3

/* Alternately: use const ints for encapsulation */    

// declare and set
int bitray = 0 | BOOL_B | BOOL_D;

// test
if (bitray & BOOL_B) cout << "Set!\n";

我想寫一個答案來再次確定並形式化這個想法:“從使用字節到使用位的轉換需要什么?” 也因為“我不在乎”這個答案在我看來是不合理的。

探索 char 與位域

同意,這很誘人。 特別是當它應該像這樣使用時:

#define FLAG_1 1
#define FLAG_2 (1 << 1)
#define FLAG_3 (1 << 2)
#define FLAG_4 (1 << 3)

struct S1 {
    char flag_1: 1;
    char flag_2: 1;
    char flag_3: 1;
    char flag_4: 1;
}; //sizeof == 1

void MyFunction(struct S1 *obj, char flags) {
    obj->flag_1 = flags & FLAG_1;
    obj->flag_2 = flags & FLAG_2;
    obj->flag_3 = flags & FLAG_3;
    obj->flag_4 = flags & FLAG_4;
    // we desire it to be as *obj = flags;
}

int main(int argc, char **argv)
{
    struct S1 obj;
    MyFunction(&obj, FLAG_1 | FLAG_2 | FLAG_3 | FLAG_4);
    
    return 0;
}

但讓我們涵蓋此類優化的所有方面。 讓我們將操作分解為更簡單的 C 命令,大致對應於匯編程序命令:

  1. 初始化所有標志。
    char flags = FLAG_1 | FLAG_3;
    //obj->flag_1 = flags & FLAG_1;
    //obj->flag_2 = flags & FLAG_2;
    //obj->flag_3 = flags & FLAG_3;
    //obj->flag_4 = flags & FLAG_4;
    *obj = flags;
  1. 寫一個標志作為常量
    //obj.flag_3 = 1;
    char a = *obj;
    a &= ~FLAG_3;
    a |= FLAG_3;
    *obj = a;
  1. 使用變量寫入單個標志
    char b = 3;
    //obj.flag_3 = b;
    char a = *obj;
    a &= ~FLAG_3;
    
    char c = b;
    c <<= 3;
    c &= ~FLAG_3; //Fixing b > 1
    
    a |= c;
    *obj = a;
  1. 將一個標志讀入變量
    //char f = obj.flag_3;
    char f = *obj;
    f >>= 3;
    f &= 0x01;
  1. 將一個標志寫入另一個標志
  //obj.flag_2 = obj.flag_4;
  char a = *obj;
  char b = a;
  a &= FLAG_4;
  a <<= 2; //Shift to FLAG_2 position
  b |= a;
  *obj = b;

恢復

命令 成本,位域 成本,可變
1.初始化 1個 4個或更少
2. obj.flag_3 = 1; 3個 1個
3. obj.flag_3 = b; 7 1 或 3 *
4. char f = obj.flag_3; 2個 1個
5. obj.flag_2 = obj.flag_4; 6個 1個

*- 如果我們保證標志不超過 1

除了初始化之外的所有操作都需要很多行代碼。 看起來我們最好在初始化后單獨保留位域))))。 但是,這通常是標志一直發生的情況。 他們在沒有警告的情況下隨機更改了 state。

我們本質上是在嘗試通過犧牲頻繁的值更改操作來降低稀有值初始化操作的成本。

在某些系統中,按位比較操作、位設置重置、位復制甚至位交換、位分支都需要一個周期。 甚至有些系統的互斥鎖定操作是由單個匯編指令實現的(在這樣的系統中,位字段可能不會位於整個 memory 區域,例如 PIC 單片機)。 無論如何,它都不是一個常見的 memory 區域。

也許在這樣的系統中,bool 類型可以指向位域的一個組件。

如果您希望節省一個字節的微不足道的位還沒有消失,請嘗試考慮實現可尋址性、操作的原子性、字節算術以及由此產生的調用開銷,數據memory,代碼memory,如果算法放在堆棧中職能。

關於選擇bool還是char的思考

如果您的目標平台將bool類型解碼為 2 個字節或 4 個或更多字節。 最有可能使用位的操作不會被優化。 相反,它是一個用於大容量計算的平台。 這意味着對它的位操作要求不高,此外,對字節和字的操作要求也不高。

與對位的操作會損害性能一樣,對單個字節的操作也會大大增加訪問變量的周期數。

沒有哪個系統可以同時對所有事情都同樣優化。 與其沉迷於明顯有大量 memory 盈余的系統中的 memory 節省,不如關注這些系統的優勢。

結論

在以下情況下使用charbool

  1. 您需要存儲可變的 state 或算法的行為(並單獨更改和返回標志)。
  2. 您的標志沒有准確描述系統,可能會演變成一個數字。
  3. 您需要能夠通過地址訪問標志。
  4. 如果您的代碼聲稱與平台無關,並且不能保證位操作將在目標平台上得到優化。

在以下情況下使用位域:

  1. 您需要存儲大量標志,而不必不斷地讀取和重寫它們。
  2. 您對 memory 的要求異常嚴格,或者 memory 要求很低。
  3. 在其他非常合理的情況下,通過計算和確認實驗。

也許一個簡短的規則可能是:

獨立標志存儲在bool中。

PS:如果你已經讀到這里並且仍然想保存 8 位中的 7 位,那么請考慮為什么不希望對最大取值為 100 的變量使用 7 位位域。

參考

Raymond Chen:布爾值集合的位域成本效益分析

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM