如何在C中将一个Int分为两个字节?

时间:2018-07-27 10:08:40

标签: c io ansi

我正在使用嵌入在最小硬件中的软件,该硬件仅支持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个字节并重新组合的解决方案都对我有好处。

非常感谢您!

7 个答案:

答案 0 :(得分:2)

这不是一个“简单”的任务。

首先,C中 byte 的数据类型为char。您可能希望在这里使用unsigned char,因为char可以是有符号的也可以是无符号的,这是实现定义的。

int是一个带符号的类型,它也使它右移也实现定义。就C而言,int必须至少具有 16位(如果char具有8位,则将是2个字节),但可以更多。但是在撰写问题时,您已经知道平台上的int有16位。在您的实现中使用这些知识意味着您的代码是特定于该平台的,而不是可移植的。

如我所见,您有两个选择:

  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的最终强制转换也是实现定义的,但是如果编译器没有,则可以在以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的规则。

  2. 您可以在内存中使用该表示形式,例如:

    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)

从到目前为止的几个答案中可以看出,有多种方法,还有一些令人惊讶的微妙之处。

  1. “数学”方法。您可以使用移位和掩码(或等效地,除法和余数)来分隔字节,并以类似方式重新组合它们。这是Felix Palmen's answer中的“选项1”。这种方法的优点是它完全独立于“字节序”问题。它的复杂之处在于它会遇到一些符号扩展和实现定义的问题。如果对复合unsigned和等式的字节分隔部分都使用int类型,这是最安全的。如果使用带符号的类型,通常将需要额外的强制类型转换和/或掩码。 (不过话说回来,这是我更喜欢的方法。)

  2. “内存”方法。您可以使用指针或union直接访问组成int的字节。这是Felix Palmen的答案中的“选项2”。这里非常重要的问题是byte order或“字节序”。另外,根据实现方式的不同,您可能会违反"strict aliasing" rule

如果您使用“数学”方法,请确保在具有和不具有各种字节集的高位的值上进行测试。例如,对于16位,完整的测试集可能包括值0x01010x01800x80010x8080。如果您编写的代码不正确(如果使用带符号的类型来实现,或者遗漏了一些其他必要的掩码),则通常会发现额外的0xff会渗入重构结果中,从而破坏传输。 (此外,您可能需要考虑编写正式的unit test,这样可以最大程度地提高重新测试代码以及​​检测到潜在错误的可能性(如果/当将其移植到计算机上,影响它的不同实现选择。)

如果您确实想传输带符号的值,则会有一些其他的麻烦。特别是,如果您在类型int大于16位的计算机上重建16位整数,则可能必须显式地对其进行符号扩展以保留其值。再次,全面的测试应确保您已充分解决了这些复杂性(至少在到目前为止已经测试过代码的平台上:-))。

回到我建议的测试值(0x01010x01800x80010x8080),如果要传输无符号整数,则它们对应于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

注意:

    根据C 2011(N1570)6.2.5 6。,
  • unsigned x; x = (unsigned) Byte1 << 8 | Byte0; memcpy(&valor, &x, sizeof valor); int具有相同的大小和对齐方式。
  • 此实现中没有unsigned的填充位,因为C要求unsigned至少为65535,所以值表示需要全部16位。
  • UINT_MAXint按照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的字节。

此外,如果将superiorinferior定义为有符号类型,则在重建过程中可能会出现问题。如果它们小于int(当然是必须的),它们将在其余重构发生之前立即被符号扩展到int,从而破坏结果。 (这就是为什么我在示例中将superiorinferior明确声明为类型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;
}