今天在寫一個協(xié)議分析程序時著拭,使用了位域,因為協(xié)議的一個數(shù)據(jù)包有的參數(shù)并不是占據(jù)n個字節(jié)(bytes)跪但,而是占據(jù)n位(bits)峦萎。
比如有1個字節(jié)包含了4個參數(shù),分別占據(jù)的位數(shù)為3,1,1,2,1被环,我就在我的數(shù)據(jù)包結(jié)構(gòu)體中定義了如下位域
struct Packet
{
// ...
uint8_t a : 3;
uint8_t b : 1;
uint8_t c : 1;
uint8_t d : 2;
uint8_t e : 1;
// ...
};
調(diào)試的時候發(fā)現(xiàn)結(jié)果不對详幽,然后寫了個測試
struct Widget
{
uint8_t a : 4;
uint8_t b : 3;
uint8_t c : 1;
void show()
{
printf("%d, %d, %d\n", a, b, c);
}
};
void func()
{
uint8_t x = 0b11100110;
auto p = (Widget*)&x;
p->show(); // output: 6, 6, 1
}
a表示前4位,b表示中間3位版姑,c表示后面1位迟郎,直觀地來看,a是1110(14)表制,b是011(3)控乾,c是0。但結(jié)果并非直觀看到的那樣壤短。
問題出在內(nèi)存布局方面慨仿,windows系統(tǒng)是小端布局,即低地址存放低字節(jié),也就是位域的順序是反過來的躲雅,即a是0110(6),b是110(6)相寇,c是1。
需要注意的是婆赠,大端小端是以字節(jié)為單位的,所以在內(nèi)存中休里,x并非存儲為
0 1 1 0 0 1 1 1 (從左向右地址依次增加)
而是針對位域而言赃承,低地址的成員(a)對應(yīng)的是字節(jié)的后半部分。
引用《C++ Primer》第5版19.8.1的說明
當(dāng)一個程序需要向其他程序或硬件設(shè)備傳遞二進制數(shù)據(jù)時拭嫁,通常會用到位域抓于。
位域在內(nèi)存中的布局是與機器相關(guān)的。
其中19.8這一小節(jié)的標(biāo)題是固有的不可移植的特性
因此使用位域的時候怕品,必須明確程序運行的機器是大端還是小端呕缭,再在代碼中定義各位域的具體順序。像Linux中/usr/include/netinet/tcp.h中定義TCP包結(jié)構(gòu)時就使用了位域
# if __BYTE_ORDER == __LITTLE_ENDIAN
u_int16_t res1:4;
u_int16_t doff:4;
u_int16_t fin:1;
u_int16_t syn:1;
u_int16_t rst:1;
u_int16_t psh:1;
u_int16_t ack:1;
u_int16_t urg:1;
u_int16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
u_int16_t doff:4;
u_int16_t res1:4;
u_int16_t res2:2;
u_int16_t urg:1;
u_int16_t ack:1;
u_int16_t psh:1;
u_int16_t rst:1;
u_int16_t syn:1;
u_int16_t fin:1;
# else
可以看到它是用系統(tǒng)定義的宏來區(qū)分大端還是小端迎罗。通過代碼跳轉(zhuǎn)可以找到__BYTE_ORDER宏的定義
#define __BYTE_ORDER __LITTLE_ENDIAN
或者纹安,在結(jié)構(gòu)體內(nèi)不使用位域砂豌,而是使用字節(jié)來存儲(比如8位),再通過位運算來計算出字節(jié)具體若干位對應(yīng)的值塔粒,比如剛才的Widget類可以改寫成下面這樣來避免機器大小端影響筐摘。
struct Widget
{
uint8_t x;
uint8_t a() const { return (x & 0xf0) >> 4; }
uint8_t b() const { return (x & 0x0e) >> 1; }
uint8_t c() const { return (x & 0x01); }
void show()
{
printf("%d, %d, %d\n", a(), b(), c());
}
};
void func()
{
uint8_t x = 0b11100110;
auto p = (Widget*)&x;
p->show(); // output: 14, 3, 0
}