如何在C中将一个Int分割成两个Bytes?

2w3rbyxf  于 2023-03-28  发布在  其他
关注(0)|答案(7)|浏览(685)

我正在使用嵌入在最小硬件中的软件,这些硬件只支持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个字节并重新组装的解决方案都很适合我。
从已经非常感谢!

2sbarzqh

2sbarzqh1#

这不是一个“简单”的任务。
首先,C语言中byte的数据类型是char。您可能希望在这里使用unsigned char,因为char可以是有符号的,也可以是无符号的,它是实现定义的。
int是有符号类型,这使得右移也是实现定义的。(如果char有8位,则为2个字节),但可以有更多。但正如您的问题所写的那样,你已经知道你的平台上的int有16位。2在你的实现中使用这些知识意味着你的代码是特定于那个平台的,而不是可移植的。
在我看来你有两个选择
1.您可以使用掩码和移位来处理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是必要的,否则,它将被提升为intint可以表示unsigned char的所有值),这在移位8位时可能会溢出。正如您已经指出的,您的int有“两个字节”,这在您的情况下是非常可能的。
最后转换为int也是 * 实现定义的 *,但如果编译器不做一些“奇怪”的事情,它将在您的“典型”平台上使用16位int作为2的补码。通过首先检查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的补码在这里很好,因为将负数int转换为unsigned的规则在C标准中有明确的规定。
1.您可以在内存中使用表示,如:

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;
rkttyhzu

rkttyhzu2#

从目前为止的几个答案中可以看出,有多种方法,其中一些可能令人惊讶的微妙之处。
1.“数学”方法。你使用移位和掩码来分离字节(或者,等价地,除法和余数),并以类似的方式重新组合它们。这是Felix Palmen的答案中的“选项1”。这种方法的优点是它完全独立于“endianness”问题。它的复杂性在于它受到一些符号扩展和实现定义的问题的影响。它最安全的做法是对公式的复合int和字节分隔部分都使用unsigned类型。如果使用有符号类型,通常需要额外的强制转换和/或掩码。(尽管如此,这是我更喜欢的方法。)
1.“内存”方法。您使用指针或union直接访问组成int的字节。这是Felix Palmen的答案中的“选项2”。这里非常重要的问题是byte order或“endianness”。此外,根据您如何实现它,您可能会与"strict aliasing" rule发生冲突。
如果使用“数学”方法,请确保在设置了和没有设置各个字节的高位的值上进行测试。例如,对于16位,完整的测试集可能包括值0x01010x01800x80010x8080。如果您没有正确编写代码,请使用“数学”方法。(如果你使用有符号类型实现它,或者如果你省略了一些其他必要的掩码),你通常会发现额外的0xff会蔓延到重建的结果中,破坏传输。(此外,您可能需要考虑编写一个正式的unit test,这样您就可以最大限度地提高代码重新测试的可能性,并检测到任何潜在的bug。如果/当它被移植到一台机器上,这台机器做出了不同的实现选择,这会影响它。)
如果你确实想传输有符号的值,你将有一些额外的复杂性。特别是,如果你在一台类型int大于16位的机器上重建16位整数,你可能必须显式地对它进行符号扩展以保留它的值。同样,全面的测试应该确保你已经充分地解决了这些复杂性(至少在你已经测试过代码的平台上:-)。
回到我建议的测试值(0x01010x01800x80010x8080),如果传输的是无符号整数,则它们对应于257、384、32769和32896。如果传输的是有符号整数,则它们对应于257、384、-32767、如果在另一端,你得到了像-693或65281这样的值(对应于十六进制0xff01),或者如果你得到了32896,而你期望的是-32640,这表明你需要回去,更小心地使用你的有符号/无符号,使用你的掩码,和/或使用你的显式符号扩展。
最后,如果你使用“内存”方法,并且如果你的发送和接收代码在不同字节顺序的机器上运行,你会发现字节被交换了。0x0102将变成0x0201。有很多方法可以解决这个问题,但它可能是一个麻烦。(这就是为什么,正如我所说,我通常更喜欢“数学”方法,这样我就可以避开字节顺序问题。)

7gs2gvoe

7gs2gvoe3#

我甚至不会写函数来做这件事。这两个操作都是C的位运算符的直接应用:

int valor = 522;
unsigned char superior = (valor >> 8) & 0xff;
unsigned char inferior = valor & 0xff;

int valorRestaurado = (superior << 8) | inferior;

虽然看起来很简单,但在编写这样的代码时总是有一些微妙之处,并且很容易出错。例如,由于valor是有符号的,因此使用>>将其右移是实现定义的,尽管通常这意味着它可能会签署扩展或不扩展,这最终不会影响& 0xff选择并分配给superior的字节的值。
此外,如果superiorinferior被定义为有符号类型,则在重构过程中可能会出现问题。(当然它们必须是),它们将在其余的重建发生之前立即被符号扩展为int,(这就是为什么我在示例中显式地将superiorinferior声明为unsigned char类型的原因。如果您的byte类型是unsigned char的typedef,也可以。)
即使superior是无符号的,在子表达式superior << 8中也可能隐藏着一个模糊的溢出,尽管它在实践中不太可能引起问题(参见Eric Postpischil的评论以获得更多解释)。

r6vfmomb

r6vfmomb4#

假定int是两个字节,并且每个字节的位数(CHAR_BIT)是8,并且使用2的补码,则名为valorint可以通过以下方式分解为endian-agnostic顺序:

unsigned x;
memcpy(&x, &valor, sizeof x);
unsigned char Byte0 = x & 0xff;
unsigned char Byte1 = x >> 8;

并且可以由X1 M4 N1 X和X1 M5 N1 X重新组装,其中:

unsigned x;
x = (unsigned) Byte1 << 8 | Byte0;
memcpy(&valor, &x, sizeof valor);

备注:

  • 根据C 2011(N1570)6.2.5 6,intunsigned具有相同的尺寸和对齐方式。
  • 在这个实现中,unsigned没有填充位,因为C要求UINT_MAX至少为65535,所以所有16位都需要用于值表示。
  • intunsigned根据www.example.com 2具有相同的字节序6.2.6.2。
  • 如果实现不是二的补码,则在同一实现中重新组装的值将恢复原始值,但负值将不能与使用不同符号位语义的实现互操作。
bvn4nwqk

bvn4nwqk5#

实际上,您可以将整数变量的地址转换为字符指针(准确地说是unsigned char*),读取值,然后递增指针指向下一个字节以再次读取值。这符合别名规则。

pbpqsu0x

pbpqsu0x6#

简单定义一个union:

typedef union
{
   int           as_int;
   unsigned char as_byte[2];
} INT2BYTE;

INT2BYTE i2b;

将整数值放入i2b.as_int成员中,并从i2b.as_byte[0]i2b.as_byte[1]中获取字节等效值。

mo49yndu

mo49yndu7#

我使用int shrot而不是int to dry,因为在PC上int是4字节,而在我的目标平台上是2字节。使用unsigned使调试更容易。
代码使用GCC编译(并且应该使用几乎任何其他C编译器进行编译)。如果我没有错,这取决于架构是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;
}

相关问题