C++:联合体 vs 位运算符

4
我有两个字符 char,我想按位将它们“拼接”在一起。
例如:
char c1 = 11; // 0000 1011
char c2 = 5;  // 0000 0101
short int si = stitch(c1, c2); // 0000 1011 0000 0101

所以,我最初尝试使用位运算符:

short int stitch(char c1, char c2)
{
    return (c1 << 8) | c2;
}

但是这并不起作用:我得到了一个等于c2的short... (1)为什么?
(但是:在我的真实应用程序中,c1c2都是负数...也许这是问题的一部分?)

所以,我的第二个解决方案是使用一个union

union stUnion
{
    struct
    {
         char c1;
         char c2;
    }
    short int si;
}

short int stitch(char c1, char c2)
{
    stUnion u;
    u.c1 = c1;
    u.c2 = c2;
    return u.si;
}

这个功能正如我所希望的那样...我想

(2) 最好/最快的方法是什么?

谢谢!

5个回答

7
union 方法最好是由具体实现定义的(实际上,它通常能可靠地工作,但 si 的格式取决于平台的字节序)。
按位操作的问题就在于负数。一个负数被表示为一串前导 1。例如,-5 就是
1111 1011

如果将此转换为int或甚至unsigned int,它就变成了。
1111 1111 11111111 1011

当使用OR时,所有这些1将淹没左移的数据。

为了解决这个问题,在进行移位操作之前,将char转换为unsigned char,然后再转换为int(以防止溢出,甚至是溢出的可能性):

short int stitch(char c1, char c2)
{
    return ( (int) (unsigned char) c1 << 8) | (unsigned char) c2;
}

或者,如果你可以自由更改参数类型并且可以包含,。
uint16_t stitch( uint8_t c1, uint8_t c2)
{
    return ( (int) c1 << 8 ) | c2;
}

3

$5.8/1规定-“操作数应为整数或枚举类型,执行整数提升。结果的类型是左操作数提升后的类型。如果右操作数为负数或大于等于左操作数提升后的位长度,则行为未定义。”

所以尝试将c1强制转换为无符号整数,然后与C2进行按位或运算。同时将输出作为无符号整数返回。字符被提升为int类型,但我们希望它成为'unsigned int'。


假设char是8位。如果不是这样,那么我的回答就不正确了。还要知道'char'是一个普通的字符类型。它可以是有符号的或无符号的,因为C++支持3种字符类型。由于您提到正在输入负值,这意味着在您的环境中'char'大多是'signed char'。 - Chubsdad

2
原因在于在执行按位或之前,c2 首先会被提升为 int 类型,这会导致符号扩展发生(假设 char 是有符号的并且可以存储负值):
char x1 = -2; // 1111 1110
char x2 = -3; // 1111 1101

short int si = stitch(c1, c2); // 1111 1111 1111 1101
x2 提升为 int 的表示形式至少有一个字节全是 1,因此它会覆盖之前你向上移动的 x1 的零位。你可以先将其转换为 unsigned char。使用二进制补码表示法,这不会改变最低字节中的位模式。虽然不是严格必要的,但你也可以将 c1 转换为 unsigned char,以保持一致性(如果 short 的长度为 2 个字节,则 c1 被符号扩展超出这两个字节是无关紧要的)。
short int stitch(char c1, char c2) {
    return ((unsigned char)c1 << 8) | (unsigned char)c2;
}

@Luther,请向提问者提出这个建议。如果我在我的答案中这样做,就会改变stitch的行为。使用CHAR_BIT将使他依赖于目标机器,这可能不是期望的(对于使用八位字节等协议)。 - Johannes Schaub - litb
当然,这意味着最好使用uintX_t类型,就像@Potatoswatter建议的那样。这些类型的可用性也将根据规范保证它们具有二进制补码表示。 - Johannes Schaub - litb

1

移位或方法比起字节顺序依赖的方法更为简洁。

此外,由于存在存储到加载转发(STLF)问题,联合方法在许多现代CPU上可能较慢。您正在将值写入内存,然后以不同的数据类型读取它。如果发生这种情况,许多CPU无法快速地将数据发送到加载器。加载器需要等待存储完全完成(退役),将其数据写入L1缓存。

在没有移位器(移位8次需要8个操作)和简单的按顺序执行的非常老旧的CPU(例如68000)上,联合方法可能会更快。


-1
你不应该使用 union。你绝对不能同时使用 union 字段。如果一个 union 有成员 A 和成员 B,那么你必须考虑到 A 和 B 是没有关系的。这是因为编译器可以在任何地方(除了结构体前面)添加填充。另一个问题是字节顺序(小端/大端)。
//编辑 上述 "union 规则" 有例外,你可以同时使用位于前面并具有相同布局的成员。例如:
union {
    struct {
        char c;
        int i;
        short s;
    } A;
    struct {
        char c;
        int i;
        char c1;
        char c2;
    } B;
};

可以同时使用 A.c 和 A.i 以及 B.c 和 B.i


标准在哪里提到“联合体”(struct->union)规则中的填充位应该放在前面? - Chubsdad
编译器不能在联合体中的“short”之前添加填充,因为联合体的地址与其每个成员的地址相同。当然,字节顺序是一个合法的问题。 - Potatoswatter
@Potatoswatter:没错,那很有道理。 - Chubsdad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接