字符是 1 个字节,整数是 4 个字节。我想将字符[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 %un", a, b, c);
输出为6513249 1633837824 6513249
哪一个是正确的?出了什么问题?
这是一个字节序问题。当您将char*
解释为int*
字符串的第一个字节成为整数的最低有效字节(因为您在小端序的 x86 上运行了此代码),而通过手动转换,第一个字节变得最重要。
要将其放入图片中,这是源数组:
a b c
+------+------+------+------+
| 0x61 | 0x62 | 0x63 | 0x00 | <---- bytes in memory
+------+------+------+------+
当这些字节在小端架构中被解释为整数时,结果是 0x00636261
,即十进制6513249。另一方面,手动放置每个字节会产生0x61626300
- 十进制1633837824。
当然,将char*
视为int*
是未定义的行为,因此差异在实践中并不重要,因为您实际上不允许使用第一次转换。但是,有一种方法可以实现相同的结果,称为类型双关语:
union {
char str[4];
unsigned int ui;
} u;
strcpy(u.str, "abc");
printf("%un", u.ui);
前两个都不正确。
第一个违反别名规则,并且可能会失败,因为str
的地址未正确对齐unsigned int
。要将字符串的字节重新解释为具有主机系统字节顺序的unsigned int
,可以使用memcpy
复制它:
unsigned int a; memcpy(&a, &str, sizeof a);
(假设unsigned int
的大小和str
的大小相同。
第二个可能会因整数溢出而失败,因为str[0]
被提升为int
,所以str[0]<<24
的类型为 int
,但移位所需的值可能大于int
中可表示的值。要解决此问题,请使用:
unsigned int b = (unsigned int) str[0] << 24 | …;
第二种方法以大端顺序解释str
中的字节,而不考虑主机系统中unsigned int
中的字节顺序。
unsigned int a = *(unsigned int*)str;
此初始化不正确,并调用未定义的行为。它违反了 C 别名规则,可能违反了处理器对齐。
你说要逐字节复制。
这意味着不允许行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("%dn", 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;
}
两者都在某种程度上是正确的:
-
您的第一个解决方案按本机字节顺序(即 CPU 使用的字节顺序)进行复制,因此可能会根据 CPU 的类型给出不同的结果。
-
无论 CPU 使用什么,您的第二个解决方案都以大端字节顺序(即最低地址处的最高有效字节)进行复制。它将在所有类型的 CPU 上产生相同的值。
正确的取决于如何解释原始数据(字符数组)。
例如,Java代码(类文件)总是使用大字节序字节序(无论CPU使用什么)。因此,如果要从 Java 类文件中读取int
,则必须使用第二种方式。在其他情况下,您可能希望使用依赖于 CPU 的方式(我认为 Matlab 以本机字节顺序将 int
写入文件,参见这个问题)。
如果您使用的是CVI(NI)编译器,则可以使用函数Scan来执行此操作:
未签名的 int a;
对于大端序:Scan(str,"%1i[b4uzi1o3210]>%i",&a);
对于小端序:Scan(str,"%1i[b4uzi1o0123]>%i",&a);
o 修饰符指定字节顺序。方括号内的 i 表示在 str 数组中的开始位置。