我正在使用嵌入在最小硬件中的软件,该硬件仅支持ANSI C,并且具有标准IO库的最小版本。
我有一个Int变量,大小为2个字节,但是我需要将其分别分成2个字节才能传输它,然后读取两个字节就可以重新组装原始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个字节并重新组合的解决方案都对我有好处。
非常感谢您!
答案 0 :(得分:2)
这不是一个“简单”的任务。
首先,C中 byte 的数据类型为char
。您可能希望在这里使用unsigned char
,因为char
可以是有符号的也可以是无符号的,这是实现定义的。
int
是一个带符号的类型,它也使它右移也实现定义。就C而言,int
必须至少具有 16位(如果char
具有8位,则将是2个字节),但可以更多。但是在撰写问题时,您已经知道平台上的int
有16位。在您的实现中使用这些知识意味着您的代码是特定于该平台的,而不是可移植的。
如我所见,您有两个选择:
您可以使用掩蔽和位移位来处理int
的值,例如:
int foo = 42;
unsigned char lsb = (unsigned)foo & 0xff; // mask the lower 8 bits
unsigned char msb = (unsigned)foo >> 8; // shift the higher 8 bits
这样做的好处是您独立于int
在内存中的布局。对于重建,请执行以下操作:
int rec = (int)(((unsigned)msb << 8) | lsb );
这里需要将msb
强制转换为unsigned
,否则将被提升为int
(int
可以表示unsigned char
的所有值),当移动8位时可能会溢出。正如您已经说过的,int
具有“两个字节”,这种情况很可能会发生。
对int
的最终强制转换也是实现定义的,但是如果编译器没有,则可以在以2的补码方式使用16位int
的“典型”平台上工作不要做“奇怪”的事情。通过首先检查unsigned
对于int
是否太大(因为原始的int
是负数),您可以避免这种情况,例如
unsigned tmp = ((unsigned)msb << 8 ) | lsb;
int rec;
if (tmp > INT_MAX)
{
tmp = ~tmp + 1; // 2's complement
if (tmp > INT_MAX)
{
// only possible when implementation uses 2's complement
// representation, and then only for INT_MIN
rec = INT_MIN;
}
else
{
rec = tmp;
rec = -rec;
}
}
else
{
rec = tmp;
}
这里的2的补码很好,因为C标准中明确规定了将负int
转换为unsigned
的规则。
您可以在内存中使用该表示形式,例如:
int foo = 42;
unsigned char *rep = (unsigned char *)&foo;
unsigned char first = rep[0];
unsigned char second = rep[1];
但是要注意first
是MSB还是LSB取决于您的计算机上使用的endianness。此外,如果您的int
包含填充位(在实践中极不可能,但C标准允许),您也会阅读它们。对于重建,请执行以下操作:
int rec;
unsigned char *recrep = (unsigned char *)&rec;
recrep[0] = first;
recrep[1] = second;
答案 1 :(得分:1)
从到目前为止的几个答案中可以看出,有多种方法,还有一些令人惊讶的微妙之处。
“数学”方法。您可以使用移位和掩码(或等效地,除法和余数)来分隔字节,并以类似方式重新组合它们。这是Felix Palmen's answer中的“选项1”。这种方法的优点是它完全独立于“字节序”问题。它的复杂之处在于它会遇到一些符号扩展和实现定义的问题。如果对复合unsigned
和等式的字节分隔部分都使用int
类型,这是最安全的。如果使用带符号的类型,通常将需要额外的强制类型转换和/或掩码。 (不过话说回来,这是我更喜欢的方法。)
“内存”方法。您可以使用指针或union
直接访问组成int
的字节。这是Felix Palmen的答案中的“选项2”。这里非常重要的问题是byte order或“字节序”。另外,根据实现方式的不同,您可能会违反"strict aliasing" rule。
如果您使用“数学”方法,请确保在具有和不具有各种字节集的高位的值上进行测试。例如,对于16位,完整的测试集可能包括值0x0101
,0x0180
,0x8001
和0x8080
。如果您编写的代码不正确(如果使用带符号的类型来实现,或者遗漏了一些其他必要的掩码),则通常会发现额外的0xff
会渗入重构结果中,从而破坏传输。 (此外,您可能需要考虑编写正式的unit test,这样可以最大程度地提高重新测试代码以及检测到潜在错误的可能性(如果/当将其移植到计算机上,影响它的不同实现选择。)
如果您确实想传输带符号的值,则会有一些其他的麻烦。特别是,如果您在类型int
大于16位的计算机上重建16位整数,则可能必须显式地对其进行符号扩展以保留其值。再次,全面的测试应确保您已充分解决了这些复杂性(至少在到目前为止已经测试过代码的平台上:-))。
回到我建议的测试值(0x0101
,0x0180
,0x8001
和0x8080
),如果要传输无符号整数,则它们对应于257 ,384、32769和32896。如果您要传输带符号整数,则它们分别对应257、384,-32767和-32640。并且在另一端,如果您获得-693或65281之类的值(对应于十六进制0xff01
),或者当您期望获得-32640时获得32896,则表明您需要回去并谨慎使用您的已签名/未签名用法,您的屏蔽和/或您的显式符号扩展名。
最后,如果您使用“内存”方法,并且您的发送和接收代码在不同字节顺序的计算机上运行,则将发现已交换的字节。 0x0102
将变成0x0201
。解决此问题的方法有很多种,但这很麻烦。 (这就是为什么,正如我所说的,我通常更喜欢“数学”方法,因此我可以回避字节顺序问题。)
答案 2 :(得分:1)
给定int
是两个字节,每个字节的位数(CHAR_BIT
)是8,并且使用二进制补码,名为int
的{{1}}可以通过以下方式分解为与字节序无关的顺序:
valor
,并可以使用以下命令从unsigned x;
memcpy(&x, &valor, sizeof x);
unsigned char Byte0 = x & 0xff;
unsigned char Byte1 = x >> 8;
和unsigned char Byte0
进行重组:
unsigned char Byte1
注意:
unsigned x;
x = (unsigned) Byte1 << 8 | Byte0;
memcpy(&valor, &x, sizeof valor);
和int
具有相同的大小和对齐方式。unsigned
的填充位,因为C要求unsigned
至少为65535,所以值表示需要全部16位。UINT_MAX
和int
按照6.2.6.2 2具有相同的字节序。答案 3 :(得分:0)
实际上,您可以将整数变量的地址转换为字符指针(准确地说是unsigned char*
),读取该值,然后将指针递增以指向下一个字节以再次读取该值。这符合别名规则。
答案 4 :(得分:0)
我什至不会编写函数来做到这一点。两种操作都是C的按位运算符的直接应用:
int valor = 522;
unsigned char superior = (valor >> 8) & 0xff;
unsigned char inferior = valor & 0xff;
int valorRestaurado = (superior << 8) | inferior;
尽管看起来很简单,但是在编写这样的代码时总会有一些微妙之处,而且很容易出错。例如,由于valor
已签名,因此使用>>
将其右移是实现定义的,尽管通常这意味着它可能会进行符号扩展或不扩展,但最终不会影响的值。 & 0xff
选择并分配给superior
的字节。
此外,如果将superior
或inferior
定义为有符号类型,则在重建过程中可能会出现问题。如果它们小于int
(当然是必须的),它们将在其余重构发生之前立即被符号扩展到int
,从而破坏结果。 (这就是为什么我在示例中将superior
和inferior
明确声明为类型unsigned char
的原因。如果您的byte
类型是unsigned char
的typedef,那就很好也是如此。)
即使superior << 8
是未签名的,子表达式superior
中也潜藏着一种模糊的溢出可能性,尽管实际上这不太可能引起问题。 (有关其他说明,请参见Eric Postpischil的评论。)
答案 5 :(得分:0)
只需定义一个联合:
typedef union
{
int as_int;
unsigned char as_byte[2];
} INT2BYTE;
INT2BYTE i2b;
将整数值放入i2b.as_int
成员中,并从i2b.as_byte[0]
和i2b.as_byte[1]
获得字节等效值。
答案 6 :(得分:-1)
我使用int shrot而不是dry
的int,因为在PC上int是4个字节,而在我的目标平台上,它们是2个字节。使用unsigned使调试更容易。
该代码使用GCC进行编译(并且应该使用几乎所有其他C编译器进行编译)。如果Im没错,则取决于体系结构是big endian
还是little endian
,但可以通过反转构造整数的行来解决:
#include <stdio.h>
void main(){
// unsigned short int = 2 bytes in a 32 bit pc
unsigned short int valor;
unsigned short int reassembled;
unsigned char data0 = 0;
unsigned char data1 = 0;
printf("An integer is %d bytes\n", sizeof(valor));
printf("Enter a number: \n");
scanf("%d",&valor);
// Decomposes the int in 2 bytes
data0 = (char) 0x00FF & valor;
data1 = (char) 0x00FF & (valor >> 8);
// Just a bit of 'feedback'
printf("Integer: %d \n", valor);
printf("Hexa: %X \n", valor);
printf("Byte 0: %d - %X \n", data0, data0);
printf("Byte 1: %d - %X \n", data1, data1);
// Reassembles the int from 2 bytes
reassembled = (unsigned short int) (data1 << 8 | data0);
// Show the rebuilt number
printf("Reassembled Integer: %d \n", reassembled);
printf("Reassembled Hexa: %X \n", reassembled);
return;
}