我正在用 C++ 编写并用 Visual C++ 2008 编译的例程中执行几个浮点运算。我还激活了优化 (/O2)。
C++ 中的代码大致如下所示:
int Calculate( CalculationParams ¶ms )
{
const ConfigurationParams& configParams = ConfigReader::Instance().Parameters();
float m1 = configParams.p1 * configParams.p2;
float m2 = configParams.p3 * configParams.p4;
float m3 = configParams.p5 * configParams.p6;
....
}
ConfigReader 是一个单例,其中包含用于计算的参数结构。这意味着由 configParams 引用来引用。
激活优化后,在极少数情况下我会得到完全不正确的结果的计算错误。
看着反汇编我看到了这个:
int Calculate( CalculationParams ¶ms )
{
...
const ConfigurationParams& configParams = ConfigReader::Instance().Parameters();
call ConfigReader::Instance()
move ebx, eax
float m1 = configParams.p1 * configParams.p2;
fld dword ptr[ebx + 0D4h]
add ebx, 8
fmul dword ptr [ebx + 0ECh]
float m2 = configParams.p3 * configParams.p2;
float m3 = configParams.p4 * configParams.p2;
...
}
首先,我们看到它没有调用Parameters()。这是可以理解的,因为参数结构位于类中,8 个字节(在另外两个浮点数之后)。因此,在调用之后,eax 具有 ConfigReader CLASS(而不是 ConfigurationParams 结构)的地址。
然后它会尝试加载一个浮点数。这就是问题发生的地方。由于某种原因,加载操作的偏移量对于指向 ConfigReader 类的 ebx 是不正确的。它应该首先添加 8 以使偏移量正确。
编译器是否有可能假定 fld 操作将比 add 操作花费更长的时间,并且在从内存加载浮点数之前,ebx 会以某种方式添加 8?这能行吗?我们偶尔出现的问题是否源于此时发生的中断并导致 ebx 在加载浮点数时没有偏移量 8?
我希望这是正确的唯一方法是将添加操作放在 fld 之前。很难理解这完全有效......
有什么办法可以关闭这种重排优化?
编辑: ConfigReader 看起来像这样
class ConfigReader
{
public:
static ConfigReader& Instance();
const ConfigurationParams& Parameters() const { return myParameters; }
private:
ConfigReader();
float internalParam1;
float internalParam2;
ConfigurationParams myParameters;
}
struct ConfigurationParams
{
char s1[10];
char s2[50];
int i1;
int i2;
int i3;
int i4;
int i5;
int i6;
int i7;
int i8;
int i9;
int i10;
int i11;
int i12;
int i13;
int i14;
int i15;
int i16;
int i17;
int i18;
int i19;
int i20;
int i21;
int i22;
int i23;
float f1;
float f2;
int i25;
int i26;
int i27;
int i28;
int i29;
int i30;
int i31;
bool b1;
float f3;
float f4;
float f5;
float p1;
float p3;
float p4;
float f9;
float f10;
float f11;
float f12;
float p2;
float f14;
float f15;
float f16;
int i32;
int i33;
int i34;
int i35;
}