如何清除struct中的填充字节以进行比较?

时间:2018-11-18 07:03:53

标签: c++ struct padding

我的结构定义如下:

struct s_zoneData {
    bool finep = true;
    double pzone_tcp = 1.0;
    double pzone_ori = 1.0;
    double pzone_eax = 1.0;
    double zone_ori  = 0.1;
    double zone_leax = 1.0;
    double zone_reax = 0.1;
};

我创建了一个比较运算符:

bool operator==(struct s_zoneData i, struct s_zoneData j) {

    return (memcmp(&i, &j, sizeof(struct s_zoneData)) == 0);

}

在大多数情况下,即使对于相同的变量,比较也会失败。我花了一些时间(并弄乱了gdb)才意识到问题是finep结构元素的填充字节是未初始化的垃圾。作为参考,在我的机器(x64)中,sizeof(struct s_zoneData)是56,这意味着finep元素有7个填充字节。

首先,我解决了对结构的每个成员使用基于ULP的浮点值比较替换memcmp的问题,因为我认为可能存在四舍五入的问题。但是,现在我想更深入地研究这个问题,并查看可能的替代解决方案。

问题是,有没有办法为不同的编译器和平台指定填充字节的值?还是将其重写为一个更笼统的问题,因为我可能过于关注自己的方法,比较两个struct s_zoneData变量的正确方法是什么?

我知道创建一个char pad[7]之类的虚拟变量并将其初始化为零应该可以解决该问题(至少对于我的特定情况而言),但是我读过很多情况,人们对于不同的情况存在结构对齐问题编译器和成员顺序,因此,我希望采用标准定义的解决方案(如果存在)。或者至少是保证不同平台和编译器兼容的东西。

3 个答案:

答案 0 :(得分:1)

尽管对于c或汇编程序员(甚至很多c ++程序员)来说,您所做的工作似乎是合乎逻辑的,但您无意中所做的却是破坏c ++对象模型并调用未定义的行为。

您可能希望根据对数据成员的引用元组来考虑值类型的比较。

比较两个这样的元组会产生正确的行为,以便进行比较和相等排序。

它们的优化效果也很好。

例如:

#include <tuple>

struct s_zoneData {
    bool finep = true;
    double pzone_tcp = 1.0;
    double pzone_ori = 1.0;
    double pzone_eax = 1.0;
    double zone_ori  = 0.1;
    double zone_leax = 1.0;
    double zone_reax = 0.1;

    friend auto as_tuple(s_zoneData const & z)
    {
        using std::tie;
        return tie(z.finep, z.pzone_tcp, z.pzone_ori, z.pzone_eax, z.zone_ori, z.zone_leax, z.zone_reax);
    }
};

auto operator ==(s_zoneData const& l, s_zoneData const& r) -> bool
{
    return as_tuple(l) == as_tuple(r);
}

汇编器输出示例:

operator==(s_zoneData const&, s_zoneData const&):
  xor eax, eax
  movzx ecx, BYTE PTR [rsi]
  cmp BYTE PTR [rdi], cl
  je .L20
  ret
.L20:
  movsd xmm0, QWORD PTR [rdi+8]
  ucomisd xmm0, QWORD PTR [rsi+8]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+16]
  ucomisd xmm0, QWORD PTR [rsi+16]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+24]
  ucomisd xmm0, QWORD PTR [rsi+24]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+32]
  ucomisd xmm0, QWORD PTR [rsi+32]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+40]
  ucomisd xmm0, QWORD PTR [rsi+40]
  jp .L13
  jne .L13
  movsd xmm0, QWORD PTR [rdi+48]
  ucomisd xmm0, QWORD PTR [rsi+48]
  mov edx, 0
  setnp al
  cmovne eax, edx
  ret
.L13:
  xor eax, eax
  ret

答案 1 :(得分:0)

将填充字节设置为可预测值的唯一方法是使用memset将整个结构设置为可预测的值-如果在将字段设置为其他值之前始终使用memset清除结构的值,那么即使复制整个结构(例如,将其作为参数传递时),也可以依靠填充字节保持不变。此外,具有静态存储持续时间的变量会将填充字节初始化为0。

答案 2 :(得分:0)

  1. #pragma pack可以删除多余的填充。
  2. 您可以通过手动添加来防止额外填充,以便可以将其显式设置为预定义的值(但是初始化必须在结构外部进行):


struct s_zoneData {
    char pad[sizeof(double)-sizeof(bool)];
    bool finep;
    double pzone_tcp;
    double pzone_ori;
    double pzone_eax;
    double zone_ori;
    double zone_leax;
    double zone_reax;
};

...
s_zoneData X = {{},true, 1.0, 1.0, 0.1, 1.0, 0.1};

编辑:根据@Guille注释,填充应与bool成员结合使用,以防止内部填充。因此,应该将填充板放置在finep之前/之后(我将示例更改为该位置),或者将finep移到结构的末尾。