【问题标题】:C/C++ packing signed char into intC/C++ 将带符号的 char 打包成 int
【发布时间】:2011-01-27 02:15:51
【问题描述】:

我需要将四个有符号字节打包成 32 位整数类型。 这就是我的想法:

int32_t byte(int8_t c) { return (unsigned char)c; }

int pack(char c0, char c1, ...) {
  return byte(c0) | byte(c1) << 8 | ...;
}

这是一个好的解决方案吗?它是便携的(不是在通信意义上)吗? 有没有现成的解决方案,也许可以提升?

我最关心的问题是将负位从 char 转换为 int 时的位顺序。我不知道正确的行为应该是什么。

谢谢

【问题讨论】:

标签: c++ c byte signed packing


【解决方案1】:

char 不保证是有符号或无符号的(在 PowerPC Linux 上,char 默认为 无符号)。传播这个词!

你想要的是这样的宏:

#include <stdint.h> /* Needed for uint32_t and uint8_t */

#define PACK(c0, c1, c2, c3) \
    (((uint32_t)(uint8_t)(c0) << 24) | \
    ((uint32_t)(uint8_t)(c1) << 16) | \
    ((uint32_t)(uint8_t)(c2) << 8) | \
    ((uint32_t)(uint8_t)(c3)))

这很丑主要是因为它不能很好地与 C 的操作顺序配合使用。此外,反斜杠返回是存在的,所以这个宏不必是一大长行。

此外,我们在转换为 uint32_t 之前转换为 uint8_t 的原因是为了防止不必要的符号扩展。

【讨论】:

  • 为什么要把c1打包3次?您可以通过将宏的参数放在括号中来解决操作顺序问题....
  • 啊——我看到你修好了。然后+1。
【解决方案2】:

我喜欢 Joey Adam 的回答,除了它是用宏编写的(在许多情况下会导致真正的痛苦),如果 'char' 不是 1 字节宽,编译器不会给你警告。这是我的解决方案(基于 Joey 的)。

inline uint32_t PACK(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) {
    return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
}

inline uint32_t PACK(sint8_t c0, sint8_t c1, sint8_t c2, sint8_t c3) {
    return PACK((uint8_t)c0, (uint8_t)c1, (uint8_t)c2, (uint8_t)c3);
}

我省略了将 c0->c3 转换为 uint32_t,因为编译器应该在转换时为您处理这个问题,并且我使用了 c 样式转换,因为它们适用于 c 或 c++(OP 标记为两者)。

【讨论】:

  • 在 C 和 C++ 中,char always 的大小为一个字节。一个字节可能不是八位。
  • +1:用于分解成两个函数并使用函数而不是宏。这是具有内联函数的理想情况。
  • 我很好奇为什么你接受一个 C++ 特性(内联函数)却又谴责另一个(改进的强制转换运算符)...... C 风格的强制转换被弃用是有原因的。否则 +1。
  • 内联函数也是一个 C 特性。
  • Grant Peters:Joey Adams 将 c0c3 显式转换为 uint32_t 的原因是,如果 int 能够表示 uint8_t 的所有值,则自动升级将是签名 int。如果你想维护unsigned,你需要请求它。
【解决方案3】:

您可以避免使用隐式转换进行强制转换:

uint32_t pack_helper(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) {
    return c0 | (c1 << 8) | (c2 << 16) | (c3 << 24);
}

uint32_t pack(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) {
    return pack_helper(c0, c1, c2, c3);
}

这个想法是您看到“正确转换所有参数。移位并组合它们”,而不是“对于每个参数,正确转换它,移位并组合它”。不过内容不多。

然后:

template <int N>
uint8_t unpack_u(uint32_t packed) {
    // cast to avoid potential warnings for implicit narrowing conversion
    return static_cast<uint8_t>(packed >> (N*8));
}

template <int N>
int8_t unpack_s(uint32_t packed) {
    uint8_t r = unpack_u<N>(packed);
    return (r <= 127 ? r : r - 256); // thanks to caf
}

int main() {
    uint32_t x = pack(4,5,6,-7);
    std::cout << (int)unpack_u<0>(x) << "\n";
    std::cout << (int)unpack_s<1>(x) << "\n";
    std::cout << (int)unpack_u<3>(x) << "\n";
    std::cout << (int)unpack_s<3>(x) << "\n";
}

输出:

4
5
249
-7

这与uint32_tuint8_tint8_t 类型一样可移植。 C99 中不需要它们,并且头文件 stdint.h 未在 C++ 或 C89 中定义。但是,如果类型存在并且满足 C99 要求,则代码将起作用。当然,在 C 中,解包函数需要函数参数而不是模板参数。如果您想编写用于解包的短循环,您可能也更喜欢在 C++ 中使用它。

为了解决类型是可选的这一事实,您可以使用uint_least32_t,这是 C99 中必需的。同样uint_least8_tint_least8_t。您将不得不更改 pack_helper 和 unpack_u 的代码:

uint_least32_t mask(uint_least32_t x) { return x & 0xFF; }

uint_least32_t pack_helper(uint_least32_t c0, uint_least32_t c1, uint_least32_t c2, uint_least32_t c3) {
    return mask(c0) | (mask(c1) << 8) | (mask(c2) << 16) | (mask(c3) << 24);
}

template <int N>
uint_least8_t unpack_u(uint_least32_t packed) {
    // cast to avoid potential warnings for implicit narrowing conversion
    return static_cast<uint_least8_t>(mask(packed >> (N*8)));
}

老实说,这不太可能值得 - 您的应用程序的其余部分可能是基于 int8_t 等确实存在的假设编写的。这是一个罕见的实现,没有 8 位和 32 位 2 的补码类型。

【讨论】:

    【解决方案4】:

    “善良”
    恕我直言,这是您将获得的最佳解决方案。编辑:虽然我会使用 static_cast&lt;unsigned int&gt; 而不是 C 风格的演员表,但我可能不会使用单独的方法来隐藏演员表....

    便携性:
    将没有可移植的方式来执行此操作,因为没有说 char 必须是 8 位,也没有说 unsigned int 必须是 4 字节宽。

    此外,您依赖字节序,因此在一个架构上打包的数据将无法在具有相反字节序的架构上使用。

    是否有现成的解决方案,也许可以提升?
    我不知道。

    【讨论】:

    • 在 C 和 C++ 中,char 的大小保证为一个字节。
    • @James McNellis:不,不是。看这里->stackoverflow.com/questions/881894/…
    • 字节序不谈,假设 chars 是 8 位,unsigned int 是 32 位,我认为即使考虑严格的别名规则,联合技巧也很好(因为 char 可以别名任何东西)。我可能错了。
    • -1 在这种情况下,联合的行为是未定义的。一些编译器不会正确解释它,并且不会正确返回“结果”。您不能从联合中的不同变量读取到最后一个写入的变量,并确保它在所有平台/编译器上都能正常工作。这不是便携式的。
    • @BillyONeal:不保证 char 的大小为 8 位。不过,它保证是一个字节的大小。 sizeof 运算符返回其操作数的大小(以字节为单位); sizeof(char) == 1 受到 C 和 C++ 标准的保证。
    【解决方案5】:

    这是基于 Grant Peters 和 Joey Adams 的回答,扩展以展示如何解包有符号值(解包函数依赖于 C 中无符号值的模规则):

    (正如 Steve Jessop 在 cmets 中指出的那样,不需要单独的 pack_spack_u 函数)。

    inline uint32_t pack(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3)
    {
        return ((uint32_t)c0 << 24) | ((uint32_t)c1 << 16) |
            ((uint32_t)c2 << 8) | (uint32_t)c3;
    }
    
    inline uint8_t unpack_c3_u(uint32_t p)
    {
        return p >> 24;
    }
    
    inline uint8_t unpack_c2_u(uint32_t p)
    {
        return p >> 16;
    }
    
    inline uint8_t unpack_c1_u(uint32_t p)
    {
        return p >> 8;
    }
    
    inline uint8_t unpack_c0_u(uint32_t p)
    {
        return p;
    }
    
    inline uint8_t unpack_c3_s(uint32_t p)
    {
        int t = unpack_c3_u(p);
        return t <= 127 ? t : t - 256;
    }
    
    inline uint8_t unpack_c2_s(uint32_t p)
    {
        int t = unpack_c2_u(p);
        return t <= 127 ? t : t - 256;
    }
    
    inline uint8_t unpack_c1_s(uint32_t p)
    {
        int t = unpack_c1_u(p);
        return t <= 127 ? t : t - 256;
    }
    
    inline uint8_t unpack_c0_s(uint32_t p)
    {
        int t = unpack_c0_u(p);
        return t <= 127 ? t : t - 256;
    }
    

    (这些是必要的,而不是简单地转换回int8_t,因为如果值超过 127,后者可能会引发实现定义的信号,因此它不是严格可移植的)。

    【讨论】:

    • pack_s 不是多余的吗?如果您使用int8_t 类型的参数表达式调用pack_u,它们将被自动转换。
    • 我认为解包中还有&lt;=
    【解决方案6】:

    您也可以让编译器为您完成工作。

    union packedchars {
      struct {
        char v1,v2,v3,v4;
      }
      int data;
    };
    
    packedchars value;
    value.data = 0;
    value.v1 = 'a';
    value.v2 = 'b;
    

    等等

    【讨论】:

    • Peters 先生评论了为什么这种方法在这个线程中是不可移植的。
    猜你喜欢
    • 2012-09-15
    • 1970-01-01
    • 2020-04-21
    • 1970-01-01
    • 2014-11-30
    • 2015-04-12
    • 1970-01-01
    • 1970-01-01
    • 2015-12-18
    相关资源
    最近更新 更多