簡體   English   中英

來自 C++ 中位域的掩碼

[英]Mask from bitfield in C++

這是一個我找不到好的答案的小謎題:

給定一個帶有位域的結構,例如

struct A {
    unsigned foo:13;
    unsigned bar:19;
};

C++ 中是否有一種(可移植的)方法來為其中一個位域獲取正確的掩碼,最好是作為編譯時常量 function 或模板?

像這樣:

constinit unsigned mask = getmask<A::bar>();  // mask should be 0xFFFFE000

理論上,在運行時,我可以粗略地做:

unsigned getmask_bar() {
    union AA {
        unsigned mask;
        A fields;
    } aa{};
    aa.fields.bar -= 1;
    return aa.mask;
}

甚至可以將其包裝在宏中(討厭。)以使其“通用”。

但我想您可以很容易地看出這種方法的各種不足之處。

有更好的通用 C++ 方法嗎? 或者甚至是一種不太好的方式? 下一個 C++ 標准有什么有用的東西嗎? 反射?

編輯:讓我補充一點,我正在嘗試找到一種使位域操作更加靈活的方法,以便程序員可以使用掩碼同時修改多個字段。 我追求簡潔的符號,這樣就可以在沒有大量樣板的情況下簡潔地表達事物。 將在 I/O 驅動程序中使用硬件寄存器作為一個用例。

不幸的是,沒有更好的方法 - 事實上,沒有辦法通過直接在 C++ 中檢查其 memory 從結構中提取單個相鄰位字段。

來自Cppreference

位域的以下屬性是實現定義的:

  • 通過使用超出范圍的值分配或初始化帶符號的位域,或將帶符號的位域遞增超過其范圍而產生的值。

  • class object 中位域的實際分配細節

    • 例如,在某些平台上,位域不跨越字節,而在其他平台上
    • 此外,在某些平台上,位字段是從左到右打包的,在其他平台上是從右到左打包的

你的編譯器可能會給你更強的保證; 但是,如果您確實依賴於特定編譯器的行為,則不能指望您的代碼可以與不同的編譯器/體系結構對一起工作。 據我所知,GCC 甚至沒有記錄它們的位字段打包,而且它因架構而異。 因此,您的代碼可能適用於 x86-64 上特定版本的 GCC,但實際上會破壞其他所有版本,包括同一編譯器的其他版本。

如果您真的希望能夠以通用方式從隨機結構中提取位域,最好的辦法是傳遞一個 function 指針(而不是掩碼); 這樣,function 就可以安全地訪問該字段並將值返回給它的調用者(或設置一個值)。

像這樣:

template<typename T>
auto extractThatBitField(const void *ptr) {
  return static_cast<const T *>(ptr)->m_thatBitField;
}

auto *extractor1 = &extractThatBitField<Type1>;
auto *extractor2 = &extractThatBitField<Type2>;
/* ... */

現在,如果你有一對{pointer, extractor} ,你可以安全地獲取位域的值。 (當然,提取器 function 必須匹配該指針后面的 object 的類型。)與使用{pointer, mask}對相比,開銷並不大; function 指針可能比 64 位機器上的掩碼大 4 個字節(如果有的話)。 提取器 function 本身將只是一個 memory 加載、一些微調和一個返回指令。 它仍然會非常快。

這是可移植的,並受 C++ 標准支持,這與直接檢查位域的位不同。

或者,C++ 允許在具有共同初始成員的標准布局結構之間進行轉換。 (盡管請記住,一旦 inheritance 或私人/受保護成員參與其中,這就會崩潰,上面的第一個解決方案也適用於所有這些情況。)

struct Common {
  int m_a : 13;
  int m_b : 19;
  int : 0; //Needed to ensure the bit fields end on a byte boundary
};

struct Type1 {
  int m_a : 13;
  int m_b : 19;
  int : 0;
  
  Whatever m_whatever;
};

struct Type2 {
  int m_a : 13;
  int m_b : 19;
  int : 0;
  
  Something m_something;
};

int getFieldA(const void *ptr) {
  //We still can't do type punning directly due
  //to weirdness in various compilers' aliasing resolution.
  //std::memcpy is the official way to do type punning.
  //This won't compile to an actual memcpy call.
  Common tmp;
  std::memcpy(&tmp, ptr, sizeof(Common));
  return tmp.m_a;
}

另請參閱: memcpy 可以用於類型雙關嗎?

暫無
暫無

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

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