【问题标题】:Returning an offset with uint16_t values返回具有 uint16_t 值的偏移量
【发布时间】:2017-10-23 19:31:55
【问题描述】:

我在尝试重新解释数据以从消息中提取信息时遇到了一些困难。我试图在这里重现问题。

我通过将它们从堆栈中弹出来接收一系列长整数(32 位)。我需要将它们组装成 4 个字(16 字节)的数据包。我在下面重新创建的结构类似于给定数据包的第一个单词。我遇到的困难是,为了确定哪个单词是起始数据包,以及我需要能够读取结构的 s5 成员中数据的八进制值的数据包类型。
简而言之,对于每条消息,我需要将第 16-31 位解释为 16 位整数,无论​​它是否跨越其他消息的位边界。

我原以为这会是一项容易得多的任务,但我似乎无法让它发挥作用。这是我尝试过的。我只是得到 Null 值。

struct S
{
    uint8_t s1  :8;
    short s2    :2;
    bool s3 :1;
    int s4  :5;
    uint16_t s5 :16;
};

int main() {
    S s;
    s.s1 = 3;
    s.s2 = 2;
    s.s3 = true;
    s.s4 = 1;
    s.s5 = 02050;
    long l;
    memcpy(&l, &s, sizeof(S));
    std::deque<long> d;
    d.push_back(l);
    cout << *((uint16_t*)(&d.front()+2)) <<endl;

【问题讨论】:

  • 您正在将 'S' 大小的内容复制到 long 中?一个长的不足以容纳所有的编辑:我很抱歉,我误读了。我仍然会检查你的结构包装并确保 sizeof(S) == sizeof (long)
  • 我们系统中的 long 是 32 位,也就是 4 个字节。
  • 是的,抱歉我发得太快了。但是你确定 sizeof(S) == sizeof(long) 吗?
  • 我确实验证了。 S 大小为 4 个字节。不过观察力不错。如果我对 s2 和 s3 使用整数,我会得到一个更大的结构,除非它被打包。
  • 位域中的位布局由实现定义。这些字段甚至可能与定义的顺序不同。

标签: c++


【解决方案1】:

如果您的流中已经有 long 值,为什么不直接使用位移?

假设数据是大端的,您可以只移开前 16 位,以获得您的八进制值:

// 69733891 is the big-endian integral value represented by
// your posted sample data, so the octal value should be 02050,
// or as an int 1064
long l = 69733891; 
uint16_t s5 = l >> 16; // shift off to get the high value (s5)

对于小端(正如您的帖子中所暗示的那样),您可以使用按位与:

uint16_t s5 = l & 0xFFFF;

为了快速比较为位移生成的程序集与指针别名,以下是 GCC 生成的内容(无优化):

为移位生成的程序集(注意SAR 是执行右移位的单个指令):

' uint16_t s5 = l >> 16;
mov    rax,QWORD PTR [rbp-0x18]
sar    rax,0x10
mov    WORD PTR [rbp-0x1a],ax

为指针别名生成的程序集:

' uint16_t s5 = *((uint16_t*)&d[0]);
lea    rax,[rbp-0x20]
mov    esi,0x0
mov    rdi,rax
call   4e <main+0x4e>
movzx  eax,WORD PTR [rax] ' this is the "4e" address called
mov    WORD PTR [rbp-0x12],ax

希望能有所帮助。

【讨论】:

  • 我认为您的意思是按位与 (&amp;),而不是异或 (^)。
  • @AdrianMcCarthy .. 好收获 :) 今天没喝第二杯咖啡 :)
  • 移位运算符不依赖于字节顺序,但是右移一个有符号的值会用符号位填充它。
  • @Bob__ .. 更正了移位运算符的字节序 .. 但如果 OP 从网络资源中提取此“流”,则字节序可能会在接收到数据时发挥作用(例如,一个系统可能不同,等等)
  • @DanFeerst .. 位移在计算上远没有指针别名那么复杂;我已经用生成的程序集更新了我的答案以显示这一点。另外,如果您获取的值由于对齐问题而需要额外读取,那么您肯定会对性能产生影响,因为额外的内存拉取以获得完整值。
【解决方案2】:

您在这里面临多个问题:

  • 位域打包由实现定义
  • 将您的 long 重新解释为 S*S&amp; 违反了严格的别名规则

如果您坚持使用 long 值,则要么必须使用有关编译器的假设,例如字节序、位打包顺序等,或者禁用字符串别名(我不推荐)。

解决方案

如果 l 是由 memcpystruct S 的实例创建的,那么将值复制回 struct S 的不同实例应该会产生完全相同的位布局.

因此,我们可以将deque 中的前端对象复制到struct S 的实例中,并检查它是s5 成员:

long f = d.front();
S sf;
memcpy(&sf, &f, sizeof(sf));
std::cout << std::oct << sf.s5 << std::endl;

【讨论】:

  • 这是一个很好的建议,但就像我上面所说的,我创建结构只是为了复制消息。我收到这个的实际方式是从一个双端队列中弹出一个长的,所以不幸的是,没有成员。
  • @DanFeerst,我知道你没有尝试我的解决方案,但它也很有效。更新答案
  • 这是未定义的行为,它违反了严格的别名规则。
  • 关于编辑,您可以将未初始化的值重新解释为不同类型的引用吗?并且第一个代码sn-p中的reinterpret_cast是因为位域中位的实现定义顺序而实现定义的吗?
  • @MillieSmith,我确定了答案。现在有什么不清楚的地方吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多