如何在具有 8 位成员的队列中前进并将对组合成 16 位值

How to advance in a queue with 8-bit members and combine couples into 16-bit values

我有一个指向数组的指针,其中包含四对 8 位值,例如这个

MSB-lsb-MSB-lsb-MSB-lsb-MSB-lsb
 ^
 |
bufPointer

我将在此队列中前进,并用这四个 8 位对填充 fooStruct 结构的 16 位成员。

对于第一个成员,我可以分两行执行此操作,如下所示:

fooStruct.M1 = *bufPointer << 8;
fooStruct.M1 |= *++bufPointer;

等等。

所以,如果我们有

MSB = 0x22;
lsb = 0x37;
hence => fooStrct.M1 = 0x2237;

是否可以在一个班轮中做到这一点?


为什么是单行? 真的没有特别的原因。学习+好奇心/以后方便的话用。 :)

为什么一行完成如此重要?

无论如何,这是一种方式:

fooStruct.M1 = (bufPointer += 2, (bufPointer[-2] << 8) | bufPointer[-1]);

当然,您可以一行完成。但我会质疑为什么,因为当代码被塞进更少的行时,它总是变得更难阅读。编译器不管你是做一个还是多个。

我认为最可读的一行是每次递增:

fooStruct.M1 = (*bufPointer++ << 8) | *bufPointer++;

但有些人可能会反对。

[请注意,根据下面的评论,以上内容已删除。我的坏建议!其他答案包含避免序列点问题的一行]

我的个人编码风格是明确表示我正在处理单个记录并使用其中的片段:

fooStruct.M1 = (bufPointer[0] << 8) | bufPointer[1];
bufPointer += 2;

但这当然是两行...任何称职的编译器都应该为这两种变体生成相同的程序集。


只是在事后编辑...从您的回答的字里行间来看,我猜您希望此代码紧凑,因为您要分配单独的结构元素并且不能在循环中执行此操作。在那种情况下:

fooStruct.M1 = (bufPointer[0] << 8) | bufPointer[1];
fooStruct.M2 = (bufPointer[2] << 8) | bufPointer[3];
fooStruct.M3 = (bufPointer[4] << 8) | bufPointer[5];
fooStruct.M4 = (bufPointer[6] << 8) | bufPointer[7];
bufPointer += 8;  // Only if you need to keep deserialising

是的,你可以在一行中完成,但为了避免序列点警告,最好这样做:

c = (*p << 8) | *(p + 1);

示例:

#include <stdio.h>

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#ifdef BUILD_64
# define BITS_PER_LONG 64
#else
# define BITS_PER_LONG 32
#endif

char *binpad (unsigned long n, size_t sz);

int main (void) {

    unsigned char a[] = { 65, 28, 59, 15 };
    unsigned char *p = NULL;
    unsigned char *end = a + sizeof a/sizeof *a;

    unsigned short c = 0;

    for (p = a; p < end; p += 2)
    {
        c = (*p << 8) | *(p + 1);

        printf ("\n *p       : %6hhu  (%s)\n", *p, binpad (*p, 16));
        printf (" *(p + 1) : %6hhu  (%s)\n", *(p + 1), binpad (*(p + 1), 16));
        printf ("\n c        : %6hu  (%s)\n", c, binpad (c, 16));
    }

    return 0;
}

char *binpad (unsigned long n, size_t sz)
{
    static char s[BITS_PER_LONG + 1] = {0};
    char *p = s + BITS_PER_LONG;
    register size_t i;

    for (i = 0; i < sz; i++)
        *(--p) = (n>>i & 1) ? '1' : '0';

    return p;
}

输出

$ ./bin/uchar2short

 *p       :     65  (0000000001000001)
 *(p + 1) :     28  (0000000000011100)

 c        :  16668  (0100000100011100)

 *p       :     59  (0000000000111011)
 *(p + 1) :     15  (0000000000001111)

 c        :  15119  (0011101100001111)

由于您使用的是 C++,因此您可以将缓冲区包装在更智能的 "reader" 中。

class Reader
{
public:
    Reader(char* bufPointer)
        : m_bufPointer(bufPointer) {}

    short shortBE()
    {
        short result = (m_bufPointer[0] << 8) | m_bufPointer[1];
        m_bufPointer += 2;
        return result;
    }

    short shortLE()
    {
        short result = (m_bufPointer[1] << 8) | m_bufPointer[0];
        m_bufPointer += 2;
        return result;
    }

    // etc...
};


FooStruct DeserializeFooStruct(char* bufPointer)
{
   Reader rd(bufPointer);

   FooStruct fooStruct;
   fooStruct.M1 = rd.shortBE();
   fooStruct.M2 = rd.shortBE();
   // and so on
   return fooStruct;
}

这种方法的一个优点是您还可以添加错误检查。例如,如果你携带缓冲区的长度,你可以检查确保没有代码试图读取缓冲区的末尾并且仍然保持调用者的单行。

已接受答案的细微变化:

c = (p[0] << 8) | p[1];

如果您使用的是大端计算机,并且您的数据以 16 位边界对齐或支持未对齐访问,那么您可以简单地:

c = *(short*)p;