9

一个字符是 1 个字节,一个整数是 4 个字节。我想将 char[4] 中的一个字节一个字节地复制到一个整数中。我想到了不同的方法,但我得到了不同的答案。

char str[4]="abc";
unsigned int a = *(unsigned int*)str;
unsigned int b = str[0]<<24 | str[1]<<16 | str[2]<<8 | str[3];
unsigned int c;
memcpy(&c, str, 4);
printf("%u %u %u\n", a, b, c);

输出为 6513249 1633837824 6513249

哪一个是正确的?出了什么问题?

4

6 回答 6

15

这是一个字节顺序问题。当您将字符串的第一个字节解释char*int*整数的最低有效字节时(因为您在 x86 上运行此代码,它是小端序),而通过手动转换,第一个字节成为最高有效字节。

要将其放入图片中,这是源数组:

   a      b      c      \0
+------+------+------+------+
| 0x61 | 0x62 | 0x63 | 0x00 |  <---- bytes in memory
+------+------+------+------+

当这些字节在小端架构中被解释为整数时,结果是0x00636261,即十进制 6513249。另一方面,手动放置每个字节会产生0x61626300- 十进制 1633837824。

当然,将 achar*视为int*未定义的行为,因此差异在实践中并不重要,因为您实际上不允许使用第一次转换。然而,有一种方法可以实现相同的结果,称为类型双关语

union {
    char str[4];
    unsigned int ui;
} u;

strcpy(u.str, "abc");
printf("%u\n", u.ui);
于 2013-10-11T17:25:33.380 回答
6

前两个都不对。

第一个违反了别名规则,并且可能会失败,因为 的地址str未正确对齐unsigned int. 要将字符串的字节重新解释为unsigned int具有主机系统字节顺序的 ,您可以使用 复制它memcpy

unsigned int a; memcpy(&a, &str, sizeof a);

(假设 anunsigned int的大小和 of 的大小str相同。)

第二个可能因整数溢出而失败,因为str[0]被提升为 an int,因此str[0]<<24类型也有int,但移位所需的值可能大于 an 中可表示的值int。要解决此问题,请使用:

unsigned int b = (unsigned int) str[0] << 24 | …;

第二种方法以str大端顺序解释字节,而不管unsigned int主机系统中字节的顺序如何。

于 2013-10-11T17:30:48.293 回答
1
unsigned int a = *(unsigned int*)str;

此初始化不正确并调用未定义的行为。它违反了 C 别名规则,可能违反了处理器对齐。

于 2013-10-11T17:25:15.650 回答
1

两者在某种程度上都是正确的:

  • 您的第一个解决方案以本机字节顺序(即 CPU 使用的字节顺序)复制,因此可能会根据 CPU 的类型给出不同的结果。

  • 无论 CPU 使用什么,您的第二个解决方案都以大端字节顺序(即最低地址的最高有效字节)复制。它将在所有类型的 CPU 上产生相同的值。

什么是正确的取决于如何解释原始数据(char 数组)。
例如,Java 代码(类文件)总是使用大端字节序(不管 CPU 使用什么)。因此,如果你想int从 Java 类文件中读取 s,你必须使用第二种方式。在其他情况下,您可能希望使用依赖于 CPU 的方式(我认为 Matlabint以本机字节顺序将 s 写入文件,参见这个问题)。

于 2013-10-11T17:25:33.040 回答
1

您说要逐字节复制。

这意味着该行unsigned int a = *(unsigned int*)str;是不允许的。但是,您正在做的是一种将数组作为不同类型读取的相当常见的方式(例如,当您从磁盘读取流时)。

它只需要一些调整:

 char * str ="abc";
int i;
unsigned a;
char * c = (char * )&a;
for(i = 0; i < sizeof(unsigned); i++){
   c[i] = str[i];
}
printf("%d\n", a);

请记住,您正在读取的数据可能与您正在读取的机器不具有相同的字节序。这可能会有所帮助:

void 
changeEndian32(void * data)
{
    uint8_t * cp = (uint8_t *) data;
    union 
    {
        uint32_t word;
        uint8_t bytes[4];
    }temp;

    temp.bytes[0] = cp[3];
    temp.bytes[1] = cp[2];
    temp.bytes[2] = cp[1];
    temp.bytes[3] = cp[0];
    *((uint32_t *)data) = temp.word;
}
于 2013-10-11T17:28:31.833 回答
0

如果您使用 CVI (National Instruments) 编译器,您可以使用函数 Scan 来执行此操作:

无符号整数a;

对于大端: Scan(str,"%1i[b4uzi1o3210]>%i",&a);

对于小端: Scan(str,"%1i[b4uzi1o0123]>%i",&a);

o 修饰符指定字节顺序。方括号内的 i 表示 str 数组中的起始位置。

于 2017-12-16T23:51:14.910 回答