我的结构定义如下:
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]
之类的虚拟变量并将其初始化为零应该可以解决该问题(至少对于我的特定情况而言),但是我读过很多情况,人们对于不同的情况存在结构对齐问题编译器和成员顺序,因此,我希望采用标准定义的解决方案(如果存在)。或者至少是保证不同平台和编译器兼容的东西。
答案 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)
#pragma pack
可以删除多余的填充。
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
移到结构的末尾。