C语言中的按位操作(0x80,0xFF,〈〈)

9bfwbjaz  于 2023-03-29  发布在  其他
关注(0)|答案(4)|浏览(215)

我理解这段代码有一个问题。我所知道的是,我们已经把一段代码传递给一个汇编程序,它把代码转换成“字节码”。现在我有一个虚拟机,它应该读这段代码。这个函数应该读第一个字节码指令。我不明白这段代码中发生了什么。我猜我们正在试图读这段字节码,但不明白它是如何完成的。

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);
}
ws51t4hk

ws51t4hk1#

这段代码写得有点糟糕,一行代码中包含了很多操作,因此包含了各种潜在的bug,看起来很脆弱。

  • bytecode[0] & 0x80简单地读取MSB符号位,假设它是2的补码或类似的,然后将其转换为布尔值。
  • 循环从最高有效字节向后迭代到最低有效字节。
  • 如果符号为负,代码将执行数据字节与0xFF的XOR。基本上反转数据中的所有位。XOR的结果是int
  • 数据字节然后将i * 8位向左移位。数据总是隐式地提升到int,因此如果i * 8恰好给予大于INT_MAX的结果,则这里存在一个未定义的行为错误。在移位之前将uint32_t转换为uint32_t,执行移位,然后转换为有符号类型。
  • 生成的int被转换为int32_t--根据系统的不同,它们可以是相同的类型或不同的类型。
  • i递增1,size递减1。
  • 如果符号为负,则int32_t被反转为符号扩展的某个2的补码负数,并且所有数据位再次被反转。除了通过左移移入的所有零也被替换为1。无论这是有意还是无意,我不知道。例如,如果你开始使用像0x0081这样的格式,你现在就有了像0xFFFF01FF这样的格式。我不知道这种格式有什么意义。

我认为bytecode[size - 1] ^ 0xFF(相当于~)是用来切换数据位的,这样当以后调用~时,它们就会切换回原来的值。如果程序员有能力的话,他们必须用注解来记录这些技巧。
无论如何,不要使用这段代码。如果目的仅仅是交换一个4字节整数的字节顺序(endianess),那么这段代码必须从头开始重写。
正确的做法是:

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;
}

任何比上面更复杂的代码都是非常有问题的。我们不必担心符号是一种特殊情况,上面的代码保留了原始的签名格式。

x6h2sr28

x6h2sr282#

所以A^0xFF切换A中设置的位,所以如果你有10101100与11111111异或..它将变成01010011。我不知道为什么他们没有在这里使用~。^是一个异或运算符,所以你是与0xFF异或。
<<是一个向上或向左的位移。换句话说,A〈〈1相当于A乘以2。
>>向下移动,因此等效于右移位或除以2。
~反转字节中的位。
注意,最好在声明时初始化变量,这样做不需要额外的处理。
sign =(t_bool)(字节码[0] & 0x 80);数字中的符号存储在第8位(或从0开始计数的位置7),这是0x 80的来源。因此,它实际上检查是否在字节码的第一个字节中设置了有符号位,如果是,则将其存储在sign变量中。
本质上,如果它是无符号的,那么它是从字节码中一次一个字节地复制字节到结果中。
如果数据是有符号的,那么它会翻转位,然后复制字节,然后当它完成复制时,它会将位翻转回来。
就我个人而言,这种事情我更喜欢得到的数据,坚持在htons()格式(网络字节顺序),然后将其存储到已分配的数组中,以端序不可知的方式存储它,然后当我检索数据时我使用ntohs()将其转换回计算机使用的格式。htons()和ntohs()是标准的C函数,一直用于网络和平台无关的数据格式化/存储/通信。

u7up0aaq

u7up0aaq3#

这个函数是一个非常简单的版本的函数,它将大端字节序转换为小端字节序。
不需要参数大小,因为它仅适用于4字节数据。
它可以更容易地通过union punning归档(并且它允许编译器优化它-在这种情况下,简单的指令):

#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

k2arahey

k2arahey4#

如果代码的目的是将网络/big-endian字节顺序中的1、2、3或4字节序列符号扩展为有符号的32位int值,则它将以困难的方式执行任务,并在此过程沿着执行reimplementing the whee l。
这可以分为三个步骤:将适当的字节数转换为32位整数值,将字节进行符号扩展为32位,然后将该32位值从big-endian转换为主机的字节顺序。
本例中重新实现的“wheel”是POSIX标准的ntohl()函数,它将big-endian/network字节顺序的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 );
}

然后,对它进行符号扩展(borrowing from this answer):

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的转换可能会导致实现定义的行为,因为可能存在无法Map到int32_t值的uint32_t值。请参见this answer
任何像样的编译器都应该将其优化为内联函数。
我个人认为这还需要更好的错误处理/输入验证。

相关问题