我正在使用嵌入在最小硬件中的软件,这些硬件只支持ANSI C,并且具有最小版本的标准IO库。
我有一个Int变量,大小为两个字节,但是我需要将它分成两个字节,以便能够传输它,然后我可以阅读这两个字节,重新组装原始的Int。
我可以像这样对每个字节进行二进制划分:
int valor = 522; // 0000 0010 0000 1010 (entero de 2 bytes)
byte superior = byteSuperior(valor); // 0000 0010
byte inferior = byteInferioror(valor); // 0000 1010
...
int valorRestaurado = bytesToInteger(superior, inferior); // 522
但我没有成功地用一种简单的方法将整体除以其重量,这给我的感觉是,它应该是微不足道的(如与位移位),我没有发现它。
实际上,任何将整体划分为2个字节并重新组装的解决方案都很适合我。
从已经非常感谢!
7条答案
按热度按时间2sbarzqh1#
这不是一个“简单”的任务。
首先,C语言中byte的数据类型是
char
。您可能希望在这里使用unsigned char
,因为char
可以是有符号的,也可以是无符号的,它是实现定义的。int
是有符号类型,这使得右移也是实现定义的。(如果char
有8位,则为2个字节),但可以有更多。但正如您的问题所写的那样,你已经知道你的平台上的int
有16位。2在你的实现中使用这些知识意味着你的代码是特定于那个平台的,而不是可移植的。在我看来你有两个选择
1.您可以使用掩码和移位来处理
int
的值,类似于:这样做的好处是,你可以独立于内存中
int
的布局。对于重建,请执行以下操作:注意这里将
msb
转换为unsigned
是必要的,否则,它将被提升为int
(int
可以表示unsigned char
的所有值),这在移位8位时可能会溢出。正如您已经指出的,您的int
有“两个字节”,这在您的情况下是非常可能的。最后转换为
int
也是 * 实现定义的 *,但如果编译器不做一些“奇怪”的事情,它将在您的“典型”平台上使用16位int
作为2的补码。通过首先检查unsigned
对于int
是否太大(因为原始的int
是负数),您可以避免这种情况,例如。2的补码在这里很好,因为将负数
int
转换为unsigned
的规则在C标准中有明确的规定。1.您可以在内存中使用表示,如:
但是要注意,
first
是MSB还是LSB取决于您的机器上使用的endianness。此外,如果您的int
包含 * 填充位 *(实际上极不可能,但C标准允许),您也会读取它们。对于重建,请执行以下操作:rkttyhzu2#
从目前为止的几个答案中可以看出,有多种方法,其中一些可能令人惊讶的微妙之处。
1.“数学”方法。你使用移位和掩码来分离字节(或者,等价地,除法和余数),并以类似的方式重新组合它们。这是Felix Palmen的答案中的“选项1”。这种方法的优点是它完全独立于“endianness”问题。它的复杂性在于它受到一些符号扩展和实现定义的问题的影响。它最安全的做法是对公式的复合
int
和字节分隔部分都使用unsigned
类型。如果使用有符号类型,通常需要额外的强制转换和/或掩码。(尽管如此,这是我更喜欢的方法。)1.“内存”方法。您使用指针或
union
直接访问组成int
的字节。这是Felix Palmen的答案中的“选项2”。这里非常重要的问题是byte order或“endianness”。此外,根据您如何实现它,您可能会与"strict aliasing" rule发生冲突。如果使用“数学”方法,请确保在设置了和没有设置各个字节的高位的值上进行测试。例如,对于16位,完整的测试集可能包括值
0x0101
,0x0180
,0x8001
和0x8080
。如果您没有正确编写代码,请使用“数学”方法。(如果你使用有符号类型实现它,或者如果你省略了一些其他必要的掩码),你通常会发现额外的0xff
会蔓延到重建的结果中,破坏传输。(此外,您可能需要考虑编写一个正式的unit test,这样您就可以最大限度地提高代码重新测试的可能性,并检测到任何潜在的bug。如果/当它被移植到一台机器上,这台机器做出了不同的实现选择,这会影响它。)如果你确实想传输有符号的值,你将有一些额外的复杂性。特别是,如果你在一台类型
int
大于16位的机器上重建16位整数,你可能必须显式地对它进行符号扩展以保留它的值。同样,全面的测试应该确保你已经充分地解决了这些复杂性(至少在你已经测试过代码的平台上:-)。回到我建议的测试值(
0x0101
、0x0180
、0x8001
和0x8080
),如果传输的是无符号整数,则它们对应于257、384、32769和32896。如果传输的是有符号整数,则它们对应于257、384、-32767、如果在另一端,你得到了像-693或65281这样的值(对应于十六进制0xff01
),或者如果你得到了32896,而你期望的是-32640,这表明你需要回去,更小心地使用你的有符号/无符号,使用你的掩码,和/或使用你的显式符号扩展。最后,如果你使用“内存”方法,并且如果你的发送和接收代码在不同字节顺序的机器上运行,你会发现字节被交换了。
0x0102
将变成0x0201
。有很多方法可以解决这个问题,但它可能是一个麻烦。(这就是为什么,正如我所说,我通常更喜欢“数学”方法,这样我就可以避开字节顺序问题。)7gs2gvoe3#
我甚至不会写函数来做这件事。这两个操作都是C的位运算符的直接应用:
虽然看起来很简单,但在编写这样的代码时总是有一些微妙之处,并且很容易出错。例如,由于
valor
是有符号的,因此使用>>
将其右移是实现定义的,尽管通常这意味着它可能会签署扩展或不扩展,这最终不会影响& 0xff
选择并分配给superior
的字节的值。此外,如果
superior
或inferior
被定义为有符号类型,则在重构过程中可能会出现问题。(当然它们必须是),它们将在其余的重建发生之前立即被符号扩展为int
,(这就是为什么我在示例中显式地将superior
和inferior
声明为unsigned char
类型的原因。如果您的byte
类型是unsigned char
的typedef,也可以。)即使
superior
是无符号的,在子表达式superior << 8
中也可能隐藏着一个模糊的溢出,尽管它在实践中不太可能引起问题(参见Eric Postpischil的评论以获得更多解释)。r6vfmomb4#
假定
int
是两个字节,并且每个字节的位数(CHAR_BIT
)是8,并且使用2的补码,则名为valor
的int
可以通过以下方式分解为endian-agnostic顺序:并且可以由X1 M4 N1 X和X1 M5 N1 X重新组装,其中:
备注:
int
和unsigned
具有相同的尺寸和对齐方式。unsigned
没有填充位,因为C要求UINT_MAX
至少为65535,所以所有16位都需要用于值表示。int
和unsigned
根据www.example.com 2具有相同的字节序6.2.6.2。bvn4nwqk5#
实际上,您可以将整数变量的地址转换为字符指针(准确地说是
unsigned char*
),读取值,然后递增指针指向下一个字节以再次读取值。这符合别名规则。pbpqsu0x6#
简单定义一个union:
将整数值放入
i2b.as_int
成员中,并从i2b.as_byte[0]
和i2b.as_byte[1]
中获取字节等效值。mo49yndu7#
我使用int shrot而不是int to
dry
,因为在PC上int是4字节,而在我的目标平台上是2字节。使用unsigned使调试更容易。代码使用GCC编译(并且应该使用几乎任何其他C编译器进行编译)。如果我没有错,这取决于架构是
big endian
还是little endian
,但可以通过反转重构整数的行来解决: