25

Using VS2012, I noticed that a switch that's been working for several years now seems to be broken in Release builds but works correctly (or at least as it used to) in Debug builds. I can't see anything at all wrong with the code so would appreciate some feedback on the correctness of using return statements from within a switch block.

The following code compiles ok but gives the wrong output in a Release build on Win7 32-bit...

#include <stdio.h>
#include <tchar.h>

class CSomeClass
{
public:
    float GetFloat(int nInt)
    {
        printf("GetFloat() - entered\n");
        switch (nInt)
        {
        case 1 :
            printf("GetFloat() - case 1 entered\n");
            return 0.5F;
        case 0 :
            printf("GetFloat() - case 0 entered\n");
            return 1.0F;
        case 2 :
            printf("GetFloat() - case 2 entered\n");
            return 2.0F;
        case 3 :
            printf("GetFloat() - case 3 entered\n");
            return 3.0F;
        case 4 :
            printf("GetFloat() - case 4 entered\n");
            return 4.0F;
        }
        printf("GetFloat() - exit\n");
        return 1.0F;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    CSomeClass pClass;
    float fValue = pClass.GetFloat(3);
    printf("fValue = %f\n", fValue);

    return 0;
}

If you can repeat the problem, and have a MS Connect login, maybe you can vote it up here too?

Actual results

Release build gives the following incorrect result:

GetFloat() - entered
GetFloat() - case 3 entered
fValue = 0.000000

Expected results

Debug build gives the following correct result:

GetFloat() - entered
GetFloat() - case 3 entered
fValue = 3.000000

MS Connect bug report

Caesar
  • 9,483
  • 8
  • 40
  • 66
Roger Rowland
  • 25,885
  • 11
  • 72
  • 113
  • 1
    Could you describe the problem you are seeing? – us2012 Feb 12 '13 at 14:53
  • 9
    What is the *wrong* and *desired* output? . – Bartek Banachewicz Feb 12 '13 at 14:54
  • 1
    Works fine for me (OS X 10.7, clang++). No surprise Microsoft made a buggy toolchain. –  Feb 12 '13 at 14:56
  • 4
    C++ will never make it an error to return from a switch case. That would break like 80% of all software ever written. – user229044 Feb 12 '13 at 14:57
  • 1
    @meagar - only 80%? It'd be about 98% of all code I've written - the remainder probably doesn't use switch or return in the same function, so should be OK. – Mats Petersson Feb 12 '13 at 14:59
  • 1
    Well I'll be, looks like a bug alright! – Josh Greifer Feb 12 '13 at 14:59
  • @MatsPetersson I was being conservative :p – user229044 Feb 12 '13 at 14:59
  • 1
    I've edited in the results from the user's bug report. – Graham Borland Feb 12 '13 at 14:59
  • Here's [**assembly comparison**](http://pastebin.com/ZregiQyY). Used `cout`, but results are similar. – Bartek Banachewicz Feb 12 '13 at 15:05
  • `std::cout << pClass.GetFloat(3);` giving the output **0**. Not sure why. – Mahesh Feb 12 '13 at 15:05
  • can you provide a disassembly of the release build? I am sure that your use of `return` is standard conform. It must be a bug on the compiler side. – example Feb 12 '13 at 15:05
  • 1
    A 64bit build has the expected result, the optimizer is screwing something up. Going from /O2 to /O1 gives correct results again on 32bit, so yeah, bug. – Xeo Feb 12 '13 at 15:06
  • 1
    @BartekBanachewicz your pastebin link is missing the disassembly of the function `GetFloat`. – example Feb 12 '13 at 15:13
  • did u contact the support directly?... not sure how urgent, but its a totally broke thing – Juarrow Feb 12 '13 at 15:15
  • Looking at the disassembly window in VS2012, there is some bad mix of FPU and SSE2 going on. Yeah, that looks like a compiler bug. –  Feb 12 '13 at 15:15
  • @example Running it through debugger really shows no error in `GetFloat` - it chooses the right `return`. – Bartek Banachewicz Feb 12 '13 at 15:16
  • 1
    Thanks everyone - looks like a definite MS compiler bug then! As others have found, 64-bit runs fine and so does 32-bit with optimisations disabled. Will have to figure out how this affects our massive codebase now ..... – Roger Rowland Feb 12 '13 at 15:18
  • 3
    @user2065121: you can try compiling with `/arch:IA32`, it makes the compiler generate working code, by disabling all SSE stuff. Explicitly enabling SSE through `/arch:SSE` and above also seems to be a fix. –  Feb 12 '13 at 15:26
  • 1
    @user2065121 Right now it seems that `/O1` is the way to go. – Bartek Banachewicz Feb 12 '13 at 15:26
  • 1
    FYI Intel C++ 13 (which is thankfully the compiler I use with VS!) does not exhibit this bug either – Josh Greifer Feb 12 '13 at 15:26
  • @Fanael - thanks for that tip - using /arch:IA32 does the trick and also explains why our legacy projects converted from VS2008 all work ok - the SSE/SSE2 default changed with VS2012 (VS2010?) so now you get it for "free" - better off without IMHO. – Roger Rowland Feb 12 '13 at 15:37
  • 1
    Roger, I have upvoted the issue on ms connect, and there I also make the comment that the problem also occurs in non-member functions, as can be checked by lifting your GetFloat() function out of the class, and removing the definition and reference to the class altogether. – Josh Greifer Feb 12 '13 at 15:42
  • 2
    This is... astounding. – Lightness Races in Orbit Feb 12 '13 at 15:44
  • @Josh - thanks, I saw your comment and have edited my post on connect accordingly - strange that I did similar but got a good result, maybe because I had other bits of the old code around it optimised differently. The /arch:IA32 suggestion by Fanael seems to indicate something broken in SSE as hvd suggested earlier. – Roger Rowland Feb 12 '13 at 15:45
  • The minimal set of options to repro the problem seems to be: `cl /GL /O2 /MD /arch:SSE2 test.cpp`. Link time code generation (`/GL`), optimization level 2 (`/O2`) and you have to link to the msvcrt.dll library (`/MD`). `/arch:SSE2` is the default setting in VS2012. Linking to the static runtime library (`/MT` - the default) prevents the problem for some reason. Changing any of those four options works around the bug (at least for this particular example). – Michael Burr Feb 12 '13 at 21:39

3 Answers3

7

It sounds like it might be this bug? Where there is a problem in returning floating point values generated in a similar way?

jcoder
  • 29,554
  • 19
  • 87
  • 130
  • Yep - that sounds like the same thing - certainly the example is almost exactly the same as mine... so this has been around for a few months already! – Roger Rowland Feb 12 '13 at 15:49
  • At least they said they'll fix it in a future release. I wonder if it's fixed in the recent update they did with some more of c++11? – jcoder Feb 12 '13 at 15:55
  • @jcoder: If you mean that November CTP, that was not an update. It's a community tech preview; known to not be production ready and only released for testing and feedback. Once they deem it ready, then it may become available as an official update. – GManNickG Feb 12 '13 at 16:42
  • 1
    Sorry yes I understand that it's not by any means a production release, I just wondered if it fixed this bug as that would be a good sign for a timely release of a fix. – jcoder Feb 12 '13 at 16:43
  • This answer lost its meaning due to the link no longer linking to a valid page. – DIF Feb 23 '18 at 06:44
6

Definitely a compiler error. Here is the stripped down asm code being executed (jumps etc. removed). The compiler removes some code it assumes to be unnecessary - even though it is not.


Release build:

// inside GetFloat
00E0104D  fld         dword ptr ds:[0E021D8h]  // load constant float onto FPU stack
00E01068  add         esp,4
00E0106B  ret 
// back in main
00E01098  cvtss2sd    xmm0,xmm0  // convert float to double. assumes the returned value to be in xmm0
00E0109C  sub         esp,8  
00E0109F  movsd       mmword ptr [esp],xmm0  // push double being printed (xmm0 is with high likelyhood = 0)
00E010A4  push        0E021B8h   // push output string
00E010A9  call        dword ptr ds:[0E02090h]  // call printf

Debug build:

003314B0  fld         dword ptr ds:[335964h]   // load const float onto FPU stack
[...]
00331500  mov         esp,ebp  
00331502  pop         ebp  
00331503  ret         4  
// back in main
00331598  fstp        dword ptr [fValue]  // copies topmost element of the FPU stack to [fValue]
0033159B  cvtss2sd    xmm0,dword ptr [fValue] // correctly takes returned value (now inside [fValue] for conversion to double 
003315A0  mov         esi,esp  
003315A2  sub         esp,8  
003315A5  movsd       mmword ptr [esp],xmm0  // push double being printed
003315AA  push        335940h  // push output string
003315AF  call        dword ptr ds:[3392C8h]  // call printf
example
  • 3,349
  • 1
  • 20
  • 29
5

Gathering data from all the results, it's most probably compiler bug.

  • x64 works correctly
  • /O1 works correctly
  • well, Debug works correctly
  • it gives the same kind of error on both cout and printf

And, probably the most important - it's 100% standards compliant. So it should work.

Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135