Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accesssing fields from a struct[] returns a very large integer

I have a C++ program that creates a two-element array of structs, then outputs the a field from one of the structs based on user input.

When user the inputs 1 to the program, it should print 0 since the a field of the second struct has a value of 0. However, after enabling optimizations and compiling with the win32 release of MSVC, it prints out 281474976710656 (2^48) instead.

Reproduction environment:

  • Visual Studio 2022 Platform Toolset v143
  • Release Win32
  • Optimization (default) (Maximum, Favor Speed) \O2
  • Language Standard c++14 (default)

The bug disappears if I turn off the optimization, change the release to debug, or switch to GCC or Clang.

#include <iostream>

struct MyData
{
    int64_t a;
    int64_t b;
};

int main()
{
    MyData dataList[2]
    {
        {1000, -1 }, /* change -1 to another number. say 3, the result will be -3 * 2^48 */
        { 0, 0 },
    };

    int i;
    std::cin >> i;
    i = i % 2;

    int64_t c = -dataList[i].a;  /* if we just do : std::cout << -dataList[i].a. the result will be correct...*/
    std::cout << c << std::endl;

    system("pause");

    return 0;
}

I believe this may be a compiler bug. Why is it occurring, and how can I avoid it?

like image 306
Shufeng Zhang Avatar asked Feb 24 '26 07:02

Shufeng Zhang


1 Answers

I agree this is a compiler bug. The emitted code is clearly wrong.

At line 1217 of the assembly, with the result of i % 2 in eax, we have:

        add     eax, eax
        mov     ecx, DWORD PTR _dataList$[ebp+eax*8]
        neg     ecx
        mov     eax, DWORD PTR _dataList$[ebp+eax+4]
        adc     eax, 0
        neg     eax
        push    eax
        push    ecx
        mov     ecx, OFFSET std::basic_ostream<char,std::char_traits<char> > std::cout ; std::cout
        call    std::basic_ostream<char,std::char_traits<char> > & std::basic_ostream<char,std::char_traits<char> >::operator<<(__int64) ; std::basic_ostream<char,std::char_traits<char> >::operator<<

The instruction mov eax, DWORD PTR _dataList$[ebp+eax+4] to load the high half of datalist[i].a is wrong and should be _dataList$[ebp+eax*8+4]. (Note that eax was already multiplied by 2 with add eax, eax, so multiplying by 8 is just what we need to account for the 16-byte size of struct MyData; the load of the low half did it correctly.)

So the load is bytes 6-9 of dataList, rather than 20-23 as it should be. That's the highest two bytes of dataList[0].a (which are 0) and the lowest two bytes of dataList[0].b which are 0xffff. Effectively, the value we end up working with is 0xffff000000000000, which is -2^48, and is then negated into 2^48.

like image 107
Nate Eldredge Avatar answered Feb 25 '26 22:02

Nate Eldredge



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!