【问题标题】:Interleave 4 byte ints to 8 byte int将 4 字节整数交错到 8 字节整数
【发布时间】:2019-02-12 05:30:33
【问题描述】:

我目前正在创建一个函数,它接受两个 4 字节无符号整数,并返回一个 8 字节无符号长整数。我试图以this research 描述的方法为基础,但我所有的尝试都没有成功。我正在使用的具体输入是:0x123456780xdeadbeef,我正在寻找的结果是0x12de34ad56be78ef。这是我迄今为止的工作:

unsigned long interleave(uint32_t x, uint32_t y){
    uint64_t result = 0;
    int shift = 33;

    for(int i = 64; i > 0; i-=16){
        shift -= 8;
        //printf("%d\n", i);
        //printf("%d\n", shift);
        result |= (x & i) << shift;
        result |= (y & i) << (shift-1);
    }
}

但是,此函数不断返回 0xfffffffe,这是不正确的。我正在使用以下方法打印和验证这些值:

printf("0x%x\n", z);

输入的初始化如下:

uint32_t x = 0x12345678;
uint32_t y = 0xdeadbeef;

非常感谢您对此主题的任何帮助,C 对我来说是一门非常困难的语言,按位运算更是如此。

【问题讨论】:

  • 相关,看看为什么你不接受你平台上的std::cout &lt;&lt; sizeof(unsigned long) 可能是有教育意义的。为什么你已经在使用uint64_t而不使用uint32_t有点奇怪。
  • 你需要使用%lx来打印一个无符号长整数。
  • 您的 i 值不是正确的掩码。并且您的移位量需要以 8 位为单位递减。
  • 将内联汇编与PSHUFB 或其等效的内在函数一起使用:(V)PSHUFB: __m128i _mm_shuffle_epi8 (__m128i a, __m128i b)
  • result 没有初始值。如果你是oring 东西,你需要先确保它是空的。 (result = 0;)

标签: c bit-manipulation interleave


【解决方案1】:

这可以基于interleaving bits 完成,但会跳过一些步骤,因此它只会交错字节。相同的想法:首先分几步展开字节,然后将它们组合起来。

这是计划,以我惊人的手绘技巧进行说明:

在 C 中(未测试):

// step 1, moving the top two bytes
uint64_t a = (((uint64_t)x & 0xFFFF0000) << 16) | (x & 0xFFFF);
// step 2, moving bytes 2 and 6
a = ((a & 0x00FF000000FF0000) << 8) | (a & 0x000000FF000000FF);
// same thing with y
uint64_t b = (((uint64_t)y & 0xFFFF0000) << 16) | (y & 0xFFFF);
b = ((b & 0x00FF000000FF0000) << 8) | (b & 0x000000FF000000FF);
// merge them
uint64_t result = (a << 8) | b;

有人建议使用 SSSE3 PSHUFB,它会起作用,但有一条指令可以一次性进行字节交错,punpcklbw。所以我们真正需要做的就是将值传入和传出向量寄存器,然后这条指令就会处理它。

未测试:

uint64_t interleave(uint32_t x, uint32_t y) {
  __m128i xvec = _mm_cvtsi32_si128(x);
  __m128i yvec = _mm_cvtsi32_si128(y);
  __m128i interleaved = _mm_unpacklo_epi8(yvec, xvec);
  return _mm_cvtsi128_si64(interleaved);
}

【讨论】:

    【解决方案2】:

    使用位移和按位操作(与字节顺序无关):

    uint64_t interleave(uint32_t x, uint32_t y){
    
        uint64_t result = 0;
    
        for(uint8_t i = 0; i < 4; i ++){
            result |= ((x & (0xFFull << (8*i))) << (8*(i+1)));
            result |= ((y & (0xFFull << (8*i))) << (8*i));
        }
    
        return result;
    }
    

    使用指针(取决于字节序):

    uint64_t interleave(uint32_t x, uint32_t y){
    
        uint64_t result = 0;
    
        uint8_t * x_ptr = (uint8_t *)&x;
        uint8_t * y_ptr = (uint8_t *)&y;
        uint8_t * r_ptr = (uint8_t *)&result;
    
        for(uint8_t i = 0; i < 4; i++){
            *(r_ptr++) = y_ptr[i];
            *(r_ptr++) = x_ptr[i];
        }
    
        return result;
    
    }
    

    注意:此解决方案采用 little-endian 字节顺序

    【讨论】:

    • 返回0x56ffffef
    • 嗯,很奇怪。在我的机器上工作正常。也测试了here
    • @PhilipDiSarro 添加了一种使用指针的方法,类似于其他答案之一,但使用了 for 循环。测试了here
    • 后面的代码,别名为uint8_t,取决于字节顺序。
    • 您的第一个答案需要对result进行零初始化。
    【解决方案3】:

    你可以这样做:

    uint64_t interleave(uint32_t x, uint32_t y)
    {
         uint64_t z;
    
         unsigned char *a = (unsigned char *)&x;   // 1
         unsigned char *b = (unsigned char *)&y;   // 1
         unsigned char *c = (unsigned char *)&z;
    
         c[0] = a[0];
         c[1] = b[0];
         c[2] = a[1];
         c[3] = b[1];
         c[4] = a[2];
         c[5] = b[2];
         c[6] = a[3];
         c[7] = b[3];
    
         return z;
    }
    

    根据订购要求,在标有1 的线上互换ab

    带有移位的版本,其中y 的 LSB 始终是输出的 LSB,如您的示例所示,是:

    uint64_t interleave(uint32_t x, uint32_t y)
    {
         return 
               (y & 0xFFull)
             | (x & 0xFFull)       << 8
             | (y & 0xFF00ull)     << 8
             | (x & 0xFF00ull)     << 16
             | (y & 0xFF0000ull)   << 16
             | (x & 0xFF0000ull)   << 24
             | (y & 0xFF000000ull) << 24
             | (x & 0xFF000000ull) << 32;
    }
    

    我尝试过的编译器似乎没有很好地优化任何一个版本,所以如果这是一个性能关键的情况,那么可能来自 cmets 的内联汇编建议是要走的路。

    【讨论】:

    • 第一个版本看机器是big-endian还是little-endian
    • 第二个版本返回0x56be78ef,这是我想要的输出的后半部分,我尝试扩展它无济于事。
    • @PhilipDiSarro 我发布的代码可以正常工作,也许你的尝试在某个地方出错了
    • @Barmar 我在 sn-p 之后的第一行中解决了这个问题
    • 您指的是“取决于订购要求”吗?你怎么知道订单是什么?
    【解决方案4】:

    使用联合双关语。便于编译器优化。

    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    
    typedef union
    {
            uint64_t u64;
            struct 
            {
                union
                {
                    uint32_t a32;
                    uint8_t a8[4]
                };
                union
                {
                    uint32_t b32;
                    uint8_t b8[4]
                };
            };
            uint8_t u8[8];
    }data_64;
    
    uint64_t interleave(uint32_t a, uint32_t b)
    {
        data_64 in , out;
    
        in.a32 = a;
        in.b32 = b;
    
    
    
        for(size_t index = 0; index < sizeof(a); index ++)
        {
    
            out.u8[index * 2 + 1] = in.a8[index];
            out.u8[index * 2 ] = in.b8[index];
        }
        return out.u64;
    }
    
    
    int main(void)
    {
    
        printf("%llx\n", interleave(0x12345678U, 0xdeadbeefU)) ;
    }
    

    【讨论】:

    • 此代码不可移植,因为它取决于字节顺序。
    • 与此处发布的所有其他内容相同 - 但非常容易修改为通用
    • 不,和其他的不一样。 M.M’s answer 包括一种不依赖于字节顺序的解决方案,而依赖于字节顺序的解决方案提到了这一点。 bigwillydos' answer 包括一个不依赖于字节顺序的解决方案,尽管它未能证明它的其他解决方案是依赖的。工程不是扔掉一些代码让自己承担风险;工程正在记录代码特征是什么(除其他外)。
    • 什么是“易于编译器优化”?如果我 try this 它不会那么好。. Clang 字面意思是在最后保持内存写入和读取,GCC 将它保存在寄存器中,但我对它的所作所为并没有留下深刻的印象。
    猜你喜欢
    • 2012-07-10
    • 2015-03-21
    • 2011-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-01
    • 1970-01-01
    • 2015-04-24
    相关资源
    最近更新 更多