簡體   English   中英

C 語言中的按位運算 (0x80, 0xFF, << )

[英]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 的補碼或類似的,然后將其轉換為布爾值。
  • 循環從最高有效字節到最低有效字節向后迭代。
  • 如果符號為負,代碼將執行數據字節與 0xFF 的異或。 基本上反轉數據中的所有位。 XOR 的結果是一個int
  • 然后將數據字節(或上述 XOR 的結果)向左移動i * 8位。 數據總是隱式提升為int ,所以如果i * 8碰巧給出大於INT_MAX的結果,這里有一個未定義的行為錯誤。 在轉換之前轉換為uint32_t ,執行轉換,然后轉換為有符號類型會更安全。
  • 結果int被轉換為int32_t - 這些可能是相同的類型或不同的類型,具體取決於系統。
  • i 增加 1,size 減少 1。
  • 如果符號為負,則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));
}

https://godbolt.org/z/rb6Na5

如果代碼的目的是將網絡/大端字節序中的 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_tint32_t的轉換可能會導致實現定義的行為,因為可能存在無法映射到int32_t值的uint32_t值。 看到這個答案

任何體面的編譯器都應該將其優化為內聯函數。

我個人認為這也需要更好的錯誤處理/輸入驗證。

暫無
暫無

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

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