【问题标题】:Copying a 4 element character array into an integer in C将 4 元素字符数组复制到 C 中的整数中
【发布时间】:2017-12-17 07:51:14
【问题描述】:

一个字符是 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

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

【问题讨论】:

  • 第一种方式类似于union,正如下面的答案所说,取决于处理器的字节序。
  • 使用printf("%08X %08X %08X\n", a, b, c); 并注意所有相同的字节是如何存在的,但顺序不同。

标签: c


【解决方案1】:

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

把它放到图片中,这是源数组:

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

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

当然,将char* 视为int* 是未定义的行为,因此在实践中差异并不重要,因为您实际上不允许使用第一次转换。然而,有一种方法可以达到相同的结果,称为type punning

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

strcpy(u.str, "abc");
printf("%u\n", u.ui);

【讨论】:

  • 谢谢。这张照片很清楚。我想要的答案是手动放置字节的答案。顺便说一句,您在数组图片中打错字了 - 0x64 而不是 0x63。
【解决方案2】:

前两个都不对。

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

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

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

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

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

第二种方法以大端顺序解释来自str 的字节,而不考虑主机系统中unsigned int 中的字节顺序。

【讨论】:

    【解决方案3】:
    unsigned int a = *(unsigned int*)str;
    

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

    【讨论】:

      【解决方案4】:

      你说过要逐字节复制。

      这意味着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;
      }
      

      【讨论】:

      • 对于联合成员,如果将某些内容存储为一种类型并提取为另一种类型,则结果取决于实现。
      • @AlterMann - 我不知道。我有兴趣了解更多。你有参考吗?我的 C 几乎总是“依赖于实现”,所以我很高兴指出这些事情。
      【解决方案5】:

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

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

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

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

      【讨论】:

      • 前两个都可能导致崩溃。这应该在任何答案中提及。两者都不正确。
      • @Eric Postpischil:第一种方式:对齐是一个完全不同的问题,与 OP 的原始问题无关。在很多情况下(即在许多硬件平台上)对齐根本不重要,这样的代码完全可以。 第二种方式:这绝对不会在任何情况下导致崩溃(无论 int 是否足够大以将值移动 24 位)
      • 对齐确实很重要,并且确实与 OP 的原始问题有关:将 char 数组别名为 int 不能保证符合对齐要求,并且可能在某些 C 实现中崩溃。在许多平台上不会崩溃的事实并不能说明问题,因为它并没有消除它在某些平台上确实崩溃的事实。
      • 第二种方式可能会在str[0] &lt;&lt; 24中溢出。 str[0]char,因此它被提升为 int(可能在不正常的 C 实现中,int 不比 char 宽)。这是一个有符号整数。然后将其移动 24 位可能会溢出int 的范围。例如,如果 str[0] 是 128,那么 str[0] &lt;&lt; 24 将是 2147483648,但 32 位有符号 int 可表示的最大值是 2147483647。C 标准没有定义带符号整数的溢出行为。程序可能会崩溃或产生不正确的结果。
      【解决方案6】:

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

      无符号整数;

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

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

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

      【讨论】:

        猜你喜欢
        • 2013-10-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-11-21
        • 2012-01-13
        • 2017-03-17
        • 2012-04-10
        • 1970-01-01
        相关资源
        最近更新 更多