【问题标题】:Copy 6 byte array to long long integer variable将 6 字节数组复制到 long long 整数变量
【发布时间】:2016-06-06 11:03:56
【问题描述】:

我从内存中读取了一个 6 字节的 unsigned char 数组。 这里的字节序是大字节序。 现在我想将存储在数组中的值分配给一个整数变量。我认为这必须是 long long,因为它必须包含最多 6 个字节。

目前我是这样分配的:

unsigned char aFoo[6];
long long nBar;
// read values to aFoo[]...
// aFoo[0]: 0x00
// aFoo[1]: 0x00
// aFoo[2]: 0x00
// aFoo[3]: 0x00
// aFoo[4]: 0x26
// aFoo[5]: 0x8e
nBar = (aFoo[0] << 64) + (aFoo[1] << 32) +(aFoo[2] << 24) + (aFoo[3] << 16) + (aFoo[4] << 8) + (aFoo[5]);

memcpy 方法会很简洁,但是当我这样做时

memcpy(&nBar, &aFoo, 6);

这 6 个字节从一开始就被复制到 long long,因此在结尾处填充零。 有没有比我的换班任务更好的方法?

【问题讨论】:

  • 试试 | (or) 而不是 + (plus) 并且不要忘记将 aFoo[0] 转换为 64 位值,因为这些操作默认情况下发生在 C 中的 32 位值上
  • (aFoo[0] &lt;&lt; 64) 是错字吗?我觉得应该是(aFoo[0] &lt;&lt; 40)
  • (aFoo[0] &lt;&lt; 64) == 0 在 64 位整数上。应该是(aFoo[0] &lt;&lt; 40)
  • memcpy 然后位移long long 是我会做的。
  • 你的班次看起来不对,例如不应该是aFoo[0] &lt;&lt; 40,而不是aFoo[0] &lt;&lt; 64 吗?

标签: c long-integer memcpy


【解决方案1】:

您想要完成的操作称为反序列化或去编组。

对于这么宽的值,使用循环是个好主意,除非你真的需要最大值。速度和你的编译器不会向量化循环:

uint8_t array[6];
...
uint64_t value = 0;

uint8_t *p = array;
for ( int i = (sizeof(array) - 1) * 8 ; i >= 0 ; i -= 8 )
    value |= (uint64_t)*p++ << i;

// 左对齐 值

注意使用stdint.h 类型和sizeof(uint8_t) cannot differ from1`。只有这些才能保证具有预期的位宽。移位值时也使用无符号整数。右移某些值是实现定义的,而左移会调用未定义的行为。

如果f你需要一个有符号的值,只需

int64_t final_value = (int64_t)value;

换档后。这仍然是实现定义的,但所有现代实现(可能是较旧的)只是复制值而不进行修改。现代编译器可能会对此进行优化,因此不会受到任何惩罚。

当然,声明可以移动。我只是将它们放在它们用于完整性的位置之前。

【讨论】:

  • 谢谢,这似乎是一个可靠的方法。然而在最后一次迭代中,i 不能低于0
  • @tzippy:我的错;我从一个递增循环开始,并使用size_t 来索引一个数组(实际上这是最正确的类型),但后来我优化了循环。请注意,您应该注意字节顺序。如给定的,你有大端;对于 little-endia,您必须反转循环(大多数时候按升序处理数组是有益的)。
  • 关于有符号值的一个补充说明:如果原始数组值被解释为有符号类型(例如,{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } 应该被解释为-1),那么需要额外的代码来处理2 个缺失字节以正确转换负值。如果是这种情况,问题有点不清楚。
  • @user694733: 0xFF 不是负值。十六进制常量是正数,并且具有可以表示它的最低等级的类型,并且至少为int。由于该值是左对齐的,因此大多数(如果不是全部)实现都会保留该符号。只有 16 个 LSB 被归零(根据问题的要求)。请注意,让字节(原文如此!)自己签名是没有用的。
  • 我不是这个意思。单个字节的符号不是重点。如果应将aFoo 解释为 48 位有符号整数(例如,从二进制文件中读取),则将其扩展为 64 位有符号整数的代码需要处理负值。在我看来,OP 希望处理前 16 个 MSB,而不是 LSB。
【解决方案2】:

你可以试试

nBar = 0;
memcpy((unsigned char*)&nBar + 2, aFoo, 6);

在数组名之前不需要&amp;,因为它已经是一个地址。

【讨论】:

  • 太糟糕了,这被否决了。由于 OP 的偏好是使用 memcpy,我认为这正是可以做到的。
  • 这是一种不好的方法,因为它做出了一些无法保证的假设。
  • @Olaf 你能告诉我假设是什么吗?
  • 例如对齐、编码、字节序。
【解决方案3】:

做你需要的正确方法是使用union

#include <stdio.h>

typedef union {
    struct {
      char padding[2];
      char aFoo[6];
    } chars;
    long long nBar;
} Combined;

int main ()
{
  Combined x;

  // reset the content of "x"
  x.nBar = 0;           // or memset(&x, 0, sizeof(x));

  // put values directly in x.chars.aFoo[]...
  x.chars.aFoo[0] = 0x00;
  x.chars.aFoo[1] = 0x00;
  x.chars.aFoo[2] = 0x00;
  x.chars.aFoo[3] = 0x00;
  x.chars.aFoo[4] = 0x26;
  x.chars.aFoo[5] = 0x8e;

  printf("nBar: %llx\n", x.nBar);

  return 0;
}

优点:代码更清晰,不用纠结位、移位、掩码等。

但是,您必须注意,出于速度优化和硬件原因,编译器可能会将填充字节挤入struct,导致aFoo 不共享nBar 所需的字节。这个小缺点可以通过告诉计算机在字节边界对齐union 的成员来解决(而不是在字边界对齐的默认值,字是 32 位或 64 位,具体取决于关于硬件架构)。

这曾经是使用#pragma 指令实现的,其确切语法取决于您使用的编译器。

自 C11/C++11 起,alignas() specifier 成为指定结构/联合成员对齐方式的标准方式(假设您的编译器已经支持它)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 2020-07-08
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多