【问题标题】:Little-endian convention, and saving to a binary fileLittle-endian 约定,并保存到二进制文件
【发布时间】:2015-02-22 23:04:13
【问题描述】:

我有一个矩阵(二维 int 指针 int **mat),我试图在 Linux 中以 Little-endian 约定写入一个文件。

这是我写入文件的函数:

#define BUFF_SIZE 4
void write_matrix(int **mat, int n, char *dest_file) {
    int i, j;
    char buff[BUFF_SIZE];
    int fd = open(dest_file, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR | S_IXUSR);

    if (fd < 0) {
        printf("Error: Could not open the file \"%s\".\n", dest_file);
    }

    buff[0] = (n & 0x000000ff);
    buff[1] = (n & 0x0000ff00) >> 8;
    buff[2] = (n & 0x00ff0000) >> 16;
    buff[3] = (n & 0xff000000) >> 24;

    write(fd, buff, BUFF_SIZE);

    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            buff[0] = (mat[i][j] & 0x000000ff);
            buff[1] = (mat[i][j] & 0x0000ff00) >> 8;
            buff[2] = (mat[i][j] & 0x00ff0000) >> 16;
            buff[3] = (mat[i][j] & 0xff000000) >> 24;

            if (write(fd, buff, BUFF_SIZE) != BUFF_SIZE) {
                close(fd);
                printf("Error: could not write to file.\n");
                return;
            }
        }
    }

    close(fd);
}

问题是,当我写出一个足够大的 mat[i][i] = i 形式的矩阵(比如 512 X 512)时,我想我得到了溢出,因为我得到了奇怪的负数。

要转换回来,我使用:

void read_matrix(int fd, int **mat, int n, char buff[]) {
    int i, j;

    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            assert(read(fd, buff, BUFF_SIZE) == BUFF_SIZE);
            mat[i][j] = byteToInt(buff);
        }
    }
}

int byteToInt(char buff[]) {
    return (buff[3] << 24) | (buff[2] << 16) | (buff[1] << 8) | (buff[0]);
}

我做错了什么?

已编辑

  1. 添加了read_matrix函数。

  2. 似乎我得到的是 short,而不是 int,因为 384 = (110000000) 变为 -128 = (bin) 1000000

  3. 做了一个测试,发现:

    字符 c = 128; 诠释 i = 0; 我 |= c;

    给出i = -128。为什么????

【问题讨论】:

  • 请展示一个调用这个的例子,它是如何设置的?
  • 很可能不是问题,但是您的错误检查很奇怪。在检查 fd 是否小于零之前写入文件。
  • 在这种情况下可能不适用,但更安全的是先移位,THEN 掩码,以避免符号扩展效应。它还使代码更具可读性,因为您总是提取最低字节,并且更紧凑,因为您不需要额外的常量。
  • 关于您的更新,如果 char 是 8 位且已签名,则 char c = 128 已经是实现定义的。 char 的有效值为-128 .. 127。您正在溢出您的 char 数据类型。 128 是 0x80,如果您的实现选择了(并且确实如此),则 -128 作为签名字符。然后将该值提示为int 以进行i|=c 计算并扩展符号,从而为您提供最终结果i=-128。我的意思是我之前所说的;不要用char 做这些东西,用unsigned char
  • 不要手动进行字节序转换。多余的:man7.org/linux/man-pages/man3/endian.3.html.而且您的代码极易出错。 int 不必是 4 字节长。

标签: c linux system-calls endianness


【解决方案1】:

问题在于您的输入转换:

int byteToInt(char buff[]) {
    return (buff[3] << 24) | (buff[2] << 16) | (buff[1] << 8) | (buff[0]);
}

您没有提及您在哪个平台上,但在大多数常见平台上char 已签名。这会导致问题。例如,假设 buff[1] 是 0x80 (0b1000000)。因为它是一个有符号值,所以它是值 -128 的代码。并且由于移位运算符首先对它们的两个参数进行整数提升,因此在执行移位操作之前将转换为整数 -128;换句话说,它的值为 0xFFFFFF80,移位后将变为 0xFFFF8000。

按位逻辑运算符(例如|)在执行按位运算之前执行通常的算术转换;在(buff[1] &lt;&lt; 8) | (buff[0]) 的情况下,左边的运算符已经是一个有符号整数(因为&lt;&lt; 的类型是它的promoted 左边参数的类型);右边的参数,一个隐式签名的char,也将被提升为一个有符号的int,所以如果它是0x80,它最终会被符号扩展为0xFFFFFF80。

在任何一种情况下,按位或运算都会以不需要的高位 1 结束。

buff[x] 显式转换为unsigned int 将无济于事,因为它首先会被符号扩展为int,然后再被重新解释为unsigned int。相反,有必要将其转换为unsigned char

int byteToInt(char buff[]) {
    return   ((unsigned char)buff[3] << 24)
           | ((unsigned char)buff[2] << 16)
           | ((unsigned char)buff[1] << 8)
           | (unsigned char)buff[0];
}

由于int可能是16位的,所以最好使用long,实际上最好使用unsigned long来避免其他转换问题。这意味着要进行双重演员:

unsigned long byteToInt(char buff[]) {
    return   ((unsigned long)(unsigned char)buff[3] << 24)
           | ((unsigned long)(unsigned char)buff[2] << 16)
           | ((unsigned long)(unsigned char)buff[1] << 8)
           | (unsigned long)(unsigned char)buff[0];
}

【讨论】:

  • (unsigned char)buff[3] &lt;&lt; 24 不可移植。 unsigned char 升级为 int。 C 将int 指定为至少 16 位。如果 int 是 16 位,则移动 int 24 是未定义的。但随后 OP 似乎假设(错误)int 至少为 32 位。
  • @chux:我相信即使 int 是 32 位,它在理论上也是不可移植的,因为不能保证左移正符号 int 会导致预期的负符号 int(尽管结果是未指定的,不是未定义的。)但是那个沼泽太深了,不能在假期里涉水。并且有一个快乐的:)
  • 在沼泽的深渊中,软泥从一个人的头上滴落下来,一边点头一边喃喃地说:“是的,换班,泥泞——非常泥泞”,他漫步回到他的巢穴并系好长袜。 (漂亮的帽子)
【解决方案2】:

您所拥有的是一种经常被忽视的未定义行为。 有符号负值的左移未定义See here了解详情。

当你这样做时

int byteToInt(char buff[]) {
    return (buff[3] << 24) | (buff[2] << 16) | (buff[1] << 8) | (buff[0]);
}

即使buff 的一个元素具有负值(即二进制数据的值之一设置 MSB),您也会遇到未定义的行为。由于您的数据是二进制的,因此将其读取为unsigned 是最有意义的。您可以使用使签名和长度明确的标准类型,例如来自stdint.huint8_t

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-06-17
    • 1970-01-01
    • 1970-01-01
    • 2020-12-01
    • 2021-04-10
    • 2013-05-13
    • 2013-09-01
    相关资源
    最近更新 更多