为什么短模量在释放模式下不正确

Why modulus of short not correct in release mode?

本文关键字:模式 不正确 释放 为什么      更新时间:2023-10-16

短整数的模数不正确。这真的很奇怪,已经花了我两天的时间。我将有问题的代码缩小如下范围(尽可能简化):

#include <stdio.h>
#include <stdlib.h>
int foo(short Width, short Height, short MSize) 
{
    short i = 0, k = 0, pos = 0;
    short j = 0;
    for(j = 1; j < Width - 1; j = j + 1)
    {/* a blank loop */}
    for(i = 1; i < Height - 1; i = i + 1) {
        for(j = 1; j < Width - 1; j = j + 1) {
            if((j % MSize) == 0) {
                k = k + 1;
            }
            printf("i=%d, k=%d, j=%d, MSize=%d, j mod MSize=%dn", (int)i, (int)k, (int)j, (int)MSize, (int)(j % MSize));
            if (pos >= 1024) {
                fprintf(stderr, "pos = %d, over 1024n", (int)pos);
            }
            pos = pos + 1;
        }
    }
    return 0;
}
int main(int argc, char* argv[])
{
    foo(32, 32, 8);
    return 0;
}

在调试模式下编译时,上述代码工作正常,j%MSize 的结果是正确的,但是,在发布模式下编译时,j%MSize 的结果将始终为 7,这是无稽之谈(在 Visual Studio 2005/2012/2013 下测试)。没有内存操作,因此不应由堆栈损坏引起。有人有头绪吗?

我看到的输出是(略有编辑):

j=10, MSize=8, j mod MSize=7
j=11, MSize=8, j mod MSize=7
j=12, MSize=8, j mod MSize=7
j=13, MSize=8, j mod MSize=7
j=14, MSize=8, j mod MSize=7
j=15, MSize=8, j mod MSize=7
j=16, MSize=8, j mod MSize=7
j=17, MSize=8, j mod MSize=7
j=18, MSize=8, j mod MSize=7
j=19, MSize=8, j mod MSize=7
j=20, MSize=8, j mod MSize=7
j=21, MSize=8, j mod MSize=7
j=22, MSize=8, j mod MSize=7
j=23, MSize=8, j mod MSize=7
j=24, MSize=8, j mod MSize=7
j=25, MSize=8, j mod MSize=7
j=26, MSize=8, j mod MSize=7
j=27, MSize=8, j mod MSize=7

以下是构建日志:

 1>Project "E:CodeworkspaceCGeneralCSNDFeatureExtractSNDFeatureExtract.vcxproj" on node 2 (Build target(s)).
 1>ClCompile:
     D:Program FilesMicrosoft Visual Studio 11.0VCbinCL.exe /c /Zi /nologo /W3 /WX- /sdl /O2 /Oi /Oy- /GL /D WIN32 /D NDEBUG /D _CONSOLE /D _MBCS /Gm- /EHsc /MT /GS /Gy /fp:precise /Zc:wchar_t /Zc:forScope /Fo"Release\" /Fd"Releasevc110.pdb" /Gd /TP /analyze- /errorReport:prompt WeirdBug.cpp
     WeirdBug.cpp
   Link:
     D:Program FilesMicrosoft Visual Studio 11.0VCbinlink.exe /ERRORREPORT:PROMPT /OUT:"E:CodeworkspaceCGeneralCReleaseSNDFeatureExtract.exe" /INCREMENTAL:NO /NOLOGO kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /MANIFEST /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /manifest:embed /DEBUG /PDB:"E:CodeworkspaceCGeneralCReleaseSNDFeatureExtract.pdb" /SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /LTCG /TLBID:1 /DYNAMICBASE /NXCOMPAT /IMPLIB:"E:CodeworkspaceCGeneralCReleaseSNDFeatureExtract.lib" /MACHINE:X86 /SAFESEH ReleaseWeirdBug.obj
     Generating code
     Finished generating code
     SNDFeatureExtract.vcxproj -> E:CodeworkspaceCGeneralCReleaseSNDFeatureExtract.exe
 1>Done Building Project "E:CodeworkspaceCGeneralCSNDFeatureExtractSNDFeatureExtract.vcxproj" (Build target(s)).

以下是 VS 的反汇编结果:

    short i = 0, k = 0, pos = 0;
    short j = 0;
    for(j = 1; j < Width - 1; j = j + 1)
00801014  mov         edi,1FF983C8h  
00801019  jl          foo+12h (0801012h)  
    {/* a blank loop */}
    for(i = 1; i < Height - 1; i = i + 1) {
0080101B  mov         edx,1  
00801020  mov         dword ptr [ebp-4],1  
00801027  mov         dword ptr [ebp-8],edx  
0080102A  and         ecx,80000007h  
00801030  jns         foo+37h (0801037h)  
00801032  dec         ecx  
00801033  or          ecx,0FFFFFFF8h  
00801036  inc         ecx  
00801037  mov         dword ptr [ebp-0Ch],ecx  
0080103A  lea         ebx,[ebx]  
00801040  mov         eax,1  
        for(j = 1; j < Width - 1; j = j + 1) {
00801045  mov         ebx,eax  
            if((j % MSize) == 0) {
00801047  test        ecx,ecx  
00801049  jne         foo+4Ch (080104Ch)  
                k = k + 1;
0080104B  inc         edi  
            }
            printf_s("i=%d, k=%d, j=%d, MSize=%d, j mod MSize=%dn", (int)i, (int)k, (int)j, (int)MSize, (int)(j % MSize));
0080104C  push        ecx  
0080104D  push        8  
0080104F  push        eax  
00801050  movsx       eax,di  
00801053  push        eax  
00801054  push        edx  
00801055  push        80CD30h  
0080105A  call        printf_s (0801266h)  
            if (pos >= 1024) {
0080105F  mov         eax,400h  
00801064  add         esp,18h  
00801067  cmp         si,ax  
0080106A  jl          foo+86h (0801086h)  
                fprintf_s(stderr, "pos = %d, over 1024n", (int)pos);
0080106C  movsx       eax,si  
                fprintf_s(stderr, "pos = %d, over 1024n", (int)pos);
0080106F  push        eax  
00801070  push        80CD5Ch  
00801075  call        __iob_func (0801175h)  
0080107A  add         eax,40h  
0080107D  push        eax  
0080107E  call        fprintf_s (080127Ch)  
00801083  add         esp,0Ch  
        for(j = 1; j < Width - 1; j = j + 1) {
00801086  mov         ecx,dword ptr [ebp-0Ch]  
00801089  mov         edx,dword ptr [ebp-8]  
            }
            pos = pos + 1;
0080108C  inc         ebx  
0080108D  movsx       eax,bx  
00801090  inc         esi  
00801091  cmp         eax,1Fh  
00801094  jl          foo+47h (0801047h)  
    {/* a blank loop */}
    for(i = 1; i < Height - 1; i = i + 1) {
00801096  mov         eax,dword ptr [ebp-4]  
00801099  inc         eax  
0080109A  movsx       edx,ax  
0080109D  mov         dword ptr [ebp-4],eax  
008010A0  mov         dword ptr [ebp-8],edx  
008010A3  cmp         edx,1Fh  
008010A6  jl          foo+40h (0801040h)  
        }
    }
    return 0;
008010A8  pop         edi  
008010A9  pop         esi  
008010AA  xor         eax,eax  
008010AC  pop         ebx  
}
008010AD  mov         esp,ebp  
008010AF  pop         ebp  
008010B0  ret  

这是因为编译器的优化,这与你的空白循环有关。但我不太确定问题出在哪里。

要简单地解决问题,请将 j 声明为:

  volatile short j;

它会正常工作。原因程序每次都会从内存中获取j而不是寄存器。

我调试了汇编代码,并发现程序计算了 j % MSize 并将其存储在空白循环之后的内存中,每次在执行 printf 之前,它只是从内存中获取值而不是重新计算它。

mov         ecx,dword ptr [ebp-10h] // j % MSize    @ memory
push        ecx  // j % MSize
mov         ecx,dword ptr [ebp-0Ch]  
push        8  // MSize
push        eax  // j
movsx       eax,word ptr [IdxY]  
movsx       esi,di  
push        esi  // k
push        eax  // IdxY
push        ecx  // i
// push static string and calling printf

但是添加一个易失性,它将像:

mov         dx,word ptr [j]  
movsx       eax,dx  // j
and         eax,80000007h  // j % 8
push        eax 
// push other vars and calling printf

这是重新计算 MOD,并将其推送到堆栈中进行 printf。所以这可能是编译器的错误,因为即使没有易失性添加,它也应该从内存中获取 j。

由于我现在无法再次添加评论:(..我发现这是/Oxxx 和/GL 标志的错误。它将从下面选择一个:

/O1 /O2 /Ox

它必须选择上述选项之一以及/GL 才能查看问题。

我的 IDE 是 Visual Studio 2010 10.0.40219.1 SP1Rel

我没有看到任何问题

$ gcc modulus.c 
$ ./a.out 
Width = 32, Height = 32, MSize = 8, Dim =16, sizeof(short)=2
i=1, IdxY=0, k=0, j=1, MSize=8, j mod MSize=1
i=1, IdxY=0, k=0, j=2, MSize=8, j mod MSize=2
i=1, IdxY=0, k=0, j=3, MSize=8, j mod MSize=3
i=1, IdxY=0, k=0, j=4, MSize=8, j mod MSize=4
i=1, IdxY=0, k=0, j=5, MSize=8, j mod MSize=5
i=1, IdxY=0, k=0, j=6, MSize=8, j mod MSize=6
i=1, IdxY=0, k=0, j=7, MSize=8, j mod MSize=7
i=1, IdxY=0, k=1, j=8, MSize=8, j mod MSize=0

我错过了什么吗?

除了已经提供的其他答案之外,我还想指出,您可以通过更好的范围来防止此类错误(尽管在这种情况下这不是您的错,但这可能是编译器错误)。

更喜欢在循环作用域内声明迭代变量。甚至更通用:仅在使用变量的范围内声明变量。

如果将第二个for循环更改为:

for(short j = 1; j < Width - 1; j = j + 1) {

为了在 for 循环的范围内声明j,编译器必须将j视为与前一个空循环无关的新变量。因此,通过重用以前的内存位置不太可能过度优化。这个小更改修复了VS2013中的错误,我认为它比使用volatile要干净得多。