[英]How to read/write arbitrary bits in C/C++
假設我有一個二進制值為 11111111 的字節 b
例如,如何讀取從第二位開始的 3 位整數值或從第五位開始寫入四位整數值?
在我問這個問題大約 2 年之后,我想以我還是一個完整的新手時想要的方式來解釋它,這對想要了解這個過程的人來說是最有益的。
首先,忘記“11111111”示例值,它並不是真正適合過程的可視化解釋。 因此,讓初始值為10111011
(十進制 187),這將更能說明該過程。
1 - 如何從第二位開始讀取 3 位值:
___ <- those 3 bits
10111011
該值為 101,即十進制的 5,有兩種可能的獲取方式:
在這種方法中,需要的位首先用值00001110
(十進制 14)屏蔽,然后將其移位:
___
10111011 AND
00001110 =
00001010 >> 1 =
___
00000101
表達式為: (value & 14) >> 1
這種方法是相似的,但操作順序是相反的,這意味着原始值被移位,然后用00000111
(7) 屏蔽,只留下最后 3 位:
___
10111011 >> 1
___
01011101 AND
00000111
00000101
表達式為: (value >> 1) & 7
這兩種方法涉及相同數量的復雜性,因此在性能上不會有差異。
2 - 如何從第二位開始寫一個 3 位值:
在這種情況下,初始值是已知的,當在代碼中出現這種情況時,您也許可以想出一種方法將已知值設置為另一個使用較少操作的已知值,但實際上這很少是在大多數情況下,代碼既不知道初始值,也不知道要編寫的值。
這意味着為了將新值成功“拼接”成字節,目標位必須設置為零,之后將移位后的值“拼接”到位,這是第一步:
___
10111011 AND
11110001 (241) =
10110001 (masked original value)
第二步是將我們要寫入的值移入 3 位,假設我們要將其從 101 (5) 更改為 110 (6)
___
00000110 << 1 =
___
00001100 (shifted "splice" value)
第三步也是最后一步是將屏蔽的原始值與移位后的“拼接”值拼接:
10110001 OR
00001100 =
___
10111101
整個過程的表達式為: (value & 241) | (6 << 1)
(value & 241) | (6 << 1)
獎勵 - 如何生成讀寫掩碼:
自然地,使用二進制到十進制轉換器遠非優雅,尤其是在 32 位和 64 位容器的情況下——十進制值變得非常大。 可以使用表達式輕松生成掩碼,編譯器可以在編譯期間有效地解析這些掩碼:
((1 << fieldLength) - 1) << (fieldIndex - 1)
,假設第一位的索引為 1(非零)(1 << fieldLength) - 1
(索引在這里不起作用,因為它總是移到第一位~
運算符反轉“掩碼和移位”掩碼表達式它是如何工作的(上面示例中的 3 位字段從第二位開始)?
00000001 << 3
00001000 - 1
00000111 << 1
00001110 ~ (read mask)
11110001 (write mask)
相同的示例適用於更寬的整數以及字段的任意位寬和位置,移位和掩碼值相應變化。
另請注意,這些示例假定無符號整數,這是您想要使用的整數作為可移植位域替代方案(標准無法保證常規位域是可移植的),左移和右移插入一個填充 0,這與右移有符號整數的情況不同。
更簡單:
使用這組宏(但僅限於 C++,因為它依賴於成員函數的生成):
#define GETMASK(index, size) ((((size_t)1 << (size)) - 1) << (index))
#define READFROM(data, index, size) (((data) & GETMASK((index), (size))) >> (index))
#define WRITETO(data, index, size, value) ((data) = (((data) & (~GETMASK((index), (size)))) | (((value) << (index)) & (GETMASK((index), (size))))))
#define FIELD(data, name, index, size) \
inline decltype(data) name() const { return READFROM(data, index, size); } \
inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); }
你可以做一些簡單的事情:
struct A {
uint bitData;
FIELD(bitData, one, 0, 1)
FIELD(bitData, two, 1, 2)
};
並將位字段實現為您可以輕松訪問的屬性:
A a;
a.set_two(3);
cout << a.two();
用 gcc 的typeof
pre-C++11 替換decltype
。
您需要移動並屏蔽該值,例如...
如果你想讀取前兩位,你只需要像這樣屏蔽掉它們:
int value = input & 0x3;
如果你想偏移它,你需要右移 N 位,然后屏蔽掉你想要的位:
int value = (intput >> 1) & 0x3;
像你在問題中問的那樣閱讀三位。
int value = (input >> 1) & 0x7;
只需使用它並隨意使用:
#define BitVal(data,y) ( (data>>y) & 1) /** Return Data.Y value **/
#define SetBit(data,y) data |= (1 << y) /** Set Data.Y to 1 **/
#define ClearBit(data,y) data &= ~(1 << y) /** Clear Data.Y to 0 **/
#define TogleBit(data,y) (data ^=BitVal(y)) /** Togle Data.Y value **/
#define Togle(data) (data =~data ) /** Togle Data value **/
例如:
uint8_t number = 0x05; //0b00000101
uint8_t bit_2 = BitVal(number,2); // bit_2 = 1
uint8_t bit_1 = BitVal(number,1); // bit_1 = 0
SetBit(number,1); // number = 0x07 => 0b00000111
ClearBit(number,2); // number =0x03 => 0b0000011
您必須執行移位和屏蔽 (AND) 操作。 令b為任意字節, p為要從中獲取n位 (>= 1) 的位的索引 (>= 0)。
首先你必須將b右移p次:
x = b >> p;
其次,你必須用n個來掩蓋結果:
mask = (1 << n) - 1;
y = x & mask;
您可以將所有內容放在一個宏中:
#define TAKE_N_BITS_FROM(b, p, n) ((b) >> (p)) & ((1 << (n)) - 1)
“例如,我如何讀取從第二位開始的 3 位整數值?”
int number = // whatever;
uint8_t val; // uint8_t is the smallest data type capable of holding 3 bits
val = (number & (1 << 2 | 1 << 3 | 1 << 4)) >> 2;
(我假設“第二位”是位#2,即實際上是第三位。)
讀取字節使用 std::bitset
const int bits_in_byte = 8;
char myChar = 's';
cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);
要編寫,您需要使用按位運算符,例如 & ^ | & << >>。 確保了解他們的工作。
例如,要獲得 00100100,您需要將第一位設置為 1,並使用 << >> 運算符將其移位 5 次。 如果你想繼續寫,你只需繼續設置第一位並移動它。 它很像一台老式打字機:你打字,然后移動紙張。
對於 00100100:將第一位設置為 1,移位 5 次,將第一位設置為 1,並移位 2 次:
const int bits_in_byte = 8;
char myChar = 0;
myChar = myChar | (0x1 << 5 | 0x1 << 2);
cout << bitset<sizeof(myChar) * bits_in_byte>(myChar);
int x = 0xFF; //your number - 11111111
例如,我如何讀取從第二位開始的 3 位整數值
int y = x & ( 0x7 << 2 ) // 0x7 is 111
// and you shift it 2 to the left
如果您一直從數據中獲取位,則可能需要使用位域。 您只需要設置一個結構並僅使用 1 和 0 加載它:
struct bitfield{
unsigned int bit : 1
}
struct bitfield *bitstream;
然后稍后像這樣加載它(用 int 或您正在加載的任何數據替換 char):
long int i;
int j, k;
unsigned char c, d;
bitstream=malloc(sizeof(struct bitfield)*charstreamlength*sizeof(char));
for (i=0; i<charstreamlength; i++){
c=charstream[i];
for(j=0; j < sizeof(char)*8; j++){
d=c;
d=d>>(sizeof(char)*8-j-1);
d=d<<(sizeof(char)*8-1);
k=d;
if(k==0){
bitstream[sizeof(char)*8*i + j].bit=0;
}else{
bitstream[sizeof(char)*8*i + j].bit=1;
}
}
}
然后訪問元素:
bitstream[bitpointer].bit=...
要么
...=bitstream[bitpointer].bit
所有這些都假設在 i86/64 上工作,而不是在 arm 上工作,因為 arm 可以是大端或小端。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.