4

Consider the following code in strange.cpp:

#include <vector> 


using namespace std;


int i = 0;


int *bar()
{   
    ++i;
    return &i; 
}   


int main()
{   
    for(size_t j = 0; j < 99999999999; ++j) // (*)
    {   
        const auto p = bar();
        if(!p) // (**)
            return -1; 
    }   
}   

Compiling this with g++ gives a warning:

$ g++ --std=c++11 -O3 strange.cpp 
strange.cpp: In function ‘int main()’:
strange.cpp:12:12: warning: iteration 4294967296ul invokes undefined behavior [-Waggressive-loop-optimizations]
         ++i;
            ^
strange.cpp:19:9: note: containing loop
         for(size_t j = 0; j < 99999999999; ++j) // (*)
         ^

I don't understand why the increment invokes undefined behavior. Moreover, there are two changes, each of which makes the warning disappear:

  1. changing the line (*) to for(int j...
  2. changing the line (**) to if(!*p)

What is the meaning of this warning, and why are the changes relevant to it?

Note

$ g++ --version
g++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
  • 1
    Your "solution" number 1 (using `int` instead of `size_t` for the loop variable) will lead to signed integer overflow, which is undefined behavior. – Some programmer dude Feb 09 '16 at 18:10
  • @JoachimPileborg not that I disagree, however it's not a solution, rather a change (admittedly problematic) to one variable, that removes a warning for a completely different variable. So thanks for the comment, but it doesn't shed light on it for me. – Ami Tavory Feb 09 '16 at 18:12
  • I did not manage to reproduce this issue with `g++ (Debian 4.7.2-5) 4.7.2`... – francis Feb 09 '16 at 19:05
  • Neither change is an actual solution - the second one causes the loop to exit immediately without ever being called. You can see this from the generated assembly code. When you use `(!*p)`, the program exits with `-1`; the optimizer detects this case from the first iteration of the loop – Anya Shenanigans Feb 09 '16 at 19:10
  • @francis I appreciate your effort in checking this, though. Thanks. – Ami Tavory Feb 09 '16 at 19:20
  • @Petesh Thanks for checking the generated assembly code (I don't know how to read it), and for your comment/answer. Just, for clarification, I did not propose these as solutions, just as things I couldn't see why their change should affect an unrelated variable. – Ami Tavory Feb 09 '16 at 19:22
  • In trying to collapse the loop in the optimizer, it actually detects the int overflow in `bar()`, which is why the complaint happens. You can explicitly define int overflow behaviour using `-fwrapv`, which will also eliminate the warning. ecatmur's answer explains the default behavior. – Anya Shenanigans Feb 09 '16 at 19:27
  • Many thanks, @Petesh! – Ami Tavory Feb 09 '16 at 19:28
  • what is `SIZE_MAX` on your platform? – M.M Feb 09 '16 at 20:29
  • @M.M How can I tell? I would normally print it, but as what type? Thanks. – Ami Tavory Feb 09 '16 at 20:40
  • @M.M I did `find /usr/include/c++ -name '*' | xargs grep 'SIZE_MAX'`, but there was no output. – Ami Tavory Feb 09 '16 at 20:45
  • `cout << SIZE_MAX << '\n';` after including iostream and climits. Or `cout << sizeof(size_t) << '\n';` – M.M Feb 09 '16 at 21:14
  • Thanks, @M.M - The output of the latter was 8. – Ami Tavory Feb 09 '16 at 21:32
  • Closely related to [Why does this loop produce “warning: iteration 3u invokes undefined behavior” and output more than 4 lines?](http://stackoverflow.com/q/24296571/1708801) – Shafik Yaghmour Dec 21 '16 at 18:10

1 Answers1

3

The increment is undefined because once i reaches std::numeric_limits<int>::max() (231 - 1 on a 32-bit, LP64 or LLP64 platform), incrementing it will overflow, which is undefined behavior for signed integral types.

gcc is warning on iteration 4294967296ul (232) rather than iteration 2147483646u (231) as you might expect, because it doesn't know the initial value of i; some other code might have run before main to set i to something other than 0. But once main is entered, no other code can run to alter i, and so once 232 iterations have completed it will have at some point reached 231 - 1 and overflowed.

  1. "fixes" it by turning the controlling condition of the loop into a tautologically true expression; this makes the loop an infinite loop, since the if inside the loop will never execute, as &i cannot be a null pointer. Infinite loops can be optimized away, so gcc eliminates the body of the loop and the integer overflow of i does not occur.

  2. "fixes" it by allowing gcc an out from the undefined behavior of integer overflow. The only way to prevent integer overflow is for i to have an initial value that is negative, such that at some point i reaches zero. This is possible (see above), and the only alternative is undefined behavior, so it must happen. So i reaches zero, the if inside the loop executes, and main returns -1.

Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    To be clear, the two "fixes" both still cause undefined behaviour, it's just that no warning is printed . There's no other code present that could modify `i` before `main` (and the compiler apparently doesn't try to use that information). – M.M Feb 09 '16 at 20:32