C / C ++将signed char打包成int

我需要将四个带符号的字节打包成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时的位顺序。 我不知道应该是什么样的正确行为。

谢谢

我喜欢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标记为两者)。

char不保证签名或未签名(在PowerPC Linux上,char默认为unsigned )。 传播这个词!

你想要的是像这样的宏:

 #include  /* 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的原因是为了防止不必要的符号扩展。

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

 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  uint8_t unpack_u(uint32_t packed) { // cast to avoid potential warnings for implicit narrowing conversion return static_cast(packed >> (N*8)); } template  int8_t unpack_s(uint32_t packed) { uint8_t r = unpack_u(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 ++中。

要解决类型是可选的这一事实,您可以使用在C99中需要的uint_least32_t 。 类似地, 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  uint_least8_t unpack_u(uint_least32_t packed) { // cast to avoid potential warnings for implicit narrowing conversion return static_cast(mask(packed >> (N*8))); } 

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

“善”
恕我直言,这是你将要获得的最好的解决方案。 编辑:虽然我使用static_cast而不是C风格的演员表,我可能不会使用单独的方法隐藏演员….

可移植性:
没有可移植的方法可以做到这一点,因为没有什么说char必须是8位,没有什么说unsigned int需要4个字节宽。

此外,您依赖于字节序,因此在一个体系结构上的数据包将无法用于具有相反字节顺序的数据包。

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

这是基于Grant Peters和Joey Adams的答案,扩展为展示如何解压缩签名值(解包函数依赖于C中无符号值的模数规则):

(正如Steve Jessop在评论中指出的那样,不需要单独的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,后者可能会引发实现定义的信号,因此它不是严格可移植的)。

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

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

等等。