[英]Bitwise operation in C language (0x80, 0xFF, << )
我在理解此代碼時遇到問題。 我所知道的是,我們已將代碼傳遞給已將代碼轉換為“字節碼”的匯編程序。 現在我有一個應該讀取此代碼的虛擬機。 這個函數應該讀取第一個字節碼指令。 我不明白這段代碼中發生了什么。 我想我們正在嘗試讀取這個字節碼,但不明白它是如何完成的。
static int32_t bytecode_to_int32(const uint8_t *bytecode, size_t size)
{
int32_t result;
t_bool sign;
int i;
result = 0;
sign = (t_bool)(bytecode[0] & 0x80);
i = 0;
while (size)
{
if (sign)
result += ((bytecode[size - 1] ^ 0xFF) << (i++ * 8));
else
result += bytecode[size - 1] << (i++ * 8);
size--;
}
if (sign)
result = ~(result);
return (result);
}
這段代碼寫得有些糟糕,在一行上進行了大量操作,因此包含各種潛在的錯誤。 它看起來很脆。
bytecode[0] & 0x80
簡單地讀取 MSB 符號位,假設它是 2 的補碼或類似的,然后將其轉換為布爾值。int
。i * 8
位。 數據總是隱式提升為int
,所以如果i * 8
碰巧給出大於INT_MAX
的結果,這里有一個未定義的行為錯誤。 在轉換之前轉換為uint32_t
,執行轉換,然后轉換為有符號類型會更安全。int
被轉換為int32_t
- 這些可能是相同的類型或不同的類型,具體取決於系統。int32_t
反轉為符號擴展的某個 2 的補碼負數,並且所有數據位再次反轉。 除了所有隨左移移入的零也被替換為 1。 這是故意的還是無意的,我無法判斷。 因此,例如,如果您從0x0081
類的內容開始,現在您將擁有0xFFFF01FF
類的0xFFFF01FF
。 這種格式有什么意義,我不知道。 我的看法是bytecode[size - 1] ^ 0xFF
(相當於~
)用於切換數據位,以便稍后在~
被調用時切換回原始值。 程序員必須用注釋記錄這些技巧,如果它們接近稱職的話。
無論如何,不要使用此代碼。 如果目的只是交換 4 字節整數的字節順序(字節序),則必須從頭開始重寫此代碼。
這正確地完成為:
static int32_t big32_to_little32 (const uint8_t* bytes)
{
uint32_t result = (uint32_t)bytes[0] << 24 |
(uint32_t)bytes[1] << 16 |
(uint32_t)bytes[2] << 8 |
(uint32_t)bytes[3] << 0 ;
return (int32_t)result;
}
任何比上述更復雜的代碼都是非常有問題的代碼。 我們不必擔心符號是特例,上面的代碼保留了原始的簽名格式。
所以A^0xFF
會切換 A 中設置的位,所以如果你有 10101100 與 11111111 異或......它會變成 01010011。我不知道他們為什么不在這里使用 ~ 。 ^ 是一個異或運算符,因此您使用 0xFF 進行異或運算。
<<
是“向上”或向左移位。 換句話說,A<<1 等價於 A 乘以 2。
>>
向下移動,因此相當於右移,或除以 2。
~
反轉字節中的位。
請注意,最好在聲明時初始化變量,這樣做不需要任何額外的處理。
符號 = (t_bool)(字節碼[0] & 0x80); 數字中的符號存儲在第 8 位(或從 0 開始計數的位置 7),這是 0x80 的來源。 所以它實際上是檢查字節碼的第一個字節中是否設置了有符號位,如果是,則將其存儲在符號變量中。
本質上,如果它是無符號的,那么它會將字節從字節碼復制到結果中,一次一個字節。
如果數據被簽名,那么它翻轉位然后復制字節,然后當它完成復制時,它將位翻轉回來。
就我個人而言,我更喜歡獲取數據,堅持 htons() 格式(網絡字節順序),然后將其 memcpy 到分配的數組,以不可知的字節序存儲,然后當我檢索數據時,我使用 ntohs () 將其轉換回計算機使用的格式。 htons() 和 ntohs() 是標准的 C 函數,一直用於網絡和平台不可知的數據格式化/存儲/通信。
該函數是將大端轉換為小端的函數的一個非常簡單的版本。
不需要參數大小,因為它僅適用於 4 字節數據。
聯合雙關語可以更容易地存檔(並且它允許編譯器對其進行優化 - 在這種情況下為簡單指令):
#define SWAP(a,b,t) do{t c = (a); (a) = (b); (b) = c;}while(0)
int32_t my_bytecode_to_int32(const uint8_t *bytecode)
{
union
{
int32_t i32;
uint8_t b8[4];
}i32;
uint8_t b;
i32.b8[3] = *bytecode++;
i32.b8[2] = *bytecode++;
i32.b8[1] = *bytecode++;
i32.b8[0] = *bytecode++;
return i32.i32;
}
int main()
{
union {
int32_t i32;
uint8_t b8[4];
}i32;
uint8_t b;
i32.i32 = -4567;
SWAP(i32.b8[0], i32.b8[3], uint8_t);
SWAP(i32.b8[1], i32.b8[2], uint8_t);
printf("%d\n", bytecode_to_int32(i32.b8, 4));
i32.i32 = -34;
SWAP(i32.b8[0], i32.b8[3], uint8_t);
SWAP(i32.b8[1], i32.b8[2], uint8_t);
printf("%d\n", my_bytecode_to_int32(i32.b8));
}
如果代碼的目的是將網絡/大端字節序中的 1 字節、2 字節、3 字節或 4 字節序列符號擴展為有符號的 32 位int
值,那么它的工作方式很困難,並且沿途重新實現輪子。
這可以分解為三個步驟:將適當數量的字節轉換為 32 位整數值,將字節符號擴展為 32 位,然后將該 32 位值從大端轉換為主機字節命令。
在這種情況下重新實現的“輪子”是 POSIX 標准的ntohl()
函數,它將大端/網絡字節順序中的 32 位無符號整數值轉換為本地主機的本機字節順序。
我要做的第一步是將 1、2、3 或 4 個字節轉換為uint32_t
:
#include <stdint.h>
#include <limits.h>
#include <arpa/inet.h>
#include <errno.h>
// convert the `size` number of bytes starting at the `bytecode` address
// to a uint32_t value
static uint32_t bytecode_to_uint32( const uint8_t *bytecode, size_t size )
{
uint32_t result = 0;
switch ( size )
{
case 4:
result = bytecode[ 0 ] << 24;
case 3:
result += bytecode[ 1 ] << 16;
case 2:
result += bytecode[ 2 ] << 8;
case 1:
result += bytecode[ 3 ];
break;
default:
// error handling here
break;
}
return( result );
}
然后,對其進行符號擴展(從這個答案中借用):
static uint32_t sign_extend_uint32( uint32_t in, size_t size );
{
if ( size == 4 )
{
return( in );
}
// being pedantic here - the existence of `[u]int32_t` pretty
// much ensures 8 bits/byte
size_t bits = size * CHAR_BIT;
uint32_t m = 1U << ( bits - 1 );
uint32_t result = ( in ^ m ) - m;
return ( result );
}
把它們放在一起:
static int32_t bytecode_to_int32( const uint8_t *bytecode, size_t size )
{
uint32_t result = bytecode_to_uint32( bytecode, size );
result = sign_extend_uint32( result, size );
// set endianness from network/big-endian to
// whatever this host's endianness is
result = ntohl( result );
// converting uint32_t here to signed int32_t
// can be subject to implementation-defined
// behavior
return( result );
}
請注意,由上述代碼中的return
語句隱式執行的從uint32_t
到int32_t
的轉換可能會導致實現定義的行為,因為可能存在無法映射到int32_t
值的uint32_t
值。 看到這個答案。
任何體面的編譯器都應該將其優化為內聯函數。
我個人認為這也需要更好的錯誤處理/輸入驗證。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.