13

In my program, I found the loop unable to exit correctly when i is int32_t. It seems like integer overflow, and is much larger than 10, and the loop does not stop. Please tell me what happened and how I can avoid this error in a large project.

#include <iostream>
#include <stdint.h>
int f(int n){

    for (int32_t i = 0; i < 10; ++i)
    {
        int64_t time = 4500000000 +  (i) * 500000000;
        std::cout << time<< " i: " << i << std::endl;

    }
    return 0;
}

int main ()
{
    return f(10);
}

code link

2
  • 7
    i*500000000 will cause signed integer overflow (undefined behaviour). To avoid this, think about the possible range of values when using multiplication Commented Apr 21, 2022 at 4:30
  • 4500000000 + (i) * 500000000; --> 4500000000 + i * (int64_t)500000000; or 4500000000 + i * UINT64_C(500000000);. Commented Apr 24, 2022 at 5:09

1 Answer 1

23

If you use GCC 11.2.0 and the -Wall -O2 options, you will see a warning about undefined behavior:

test.cpp: In function 'int f(int)':
test.cpp:7:42: warning: iteration 5 invokes undefined behavior [-Waggressive-loop-optimizations]
    7 |         int64_t time = 4500000000 +  (i) * 500000000;
      |                                      ~~~~^~~~~~~~~~~
test.cpp:5:27: note: within this loop
    5 |     for (int32_t i = 0; i < 10; ++i)
      |                         ~~^~~~

The compiler knows that 5 * 500000000 is too large to fit in an int (which is typically 32-bit). Signed integer overflow is undefined behavior in C++. Therefore, the compiler is free to assume that this overflow never happens, so it will assume that i can never reach 10, so it can get rid of the part of your for loop that checks i < 10. I know that sounds crazy, but if your program does undefined behavior, the compiler is free to do whatever it wants.

Just add some casts to specify that you want to do 64-bit arithmetic. This eliminates the warnings, the overflows, and the undefined behavior:

int64_t time = (int64_t)4500000000 + i * (int64_t)500000000;

Update: For a larger project that could have more bugs, you might consider using GCC's -fwrapv option, which makes the behavior of signed integer overflow be defined. You could also use -fsanitize=signed-integer-overflow or -fsanitize=undefined to detect these issues at run time, if your toolchain supports those options.

Sign up to request clarification or add additional context in comments.

18 Comments

you just need 4500000000LL instead of (int64_t)4500000000. Same to 500000000LL
You generally shouldn't be C-style casting in C++. std::int64_t{4500000000} seems most right to me here
The long long type is guranteed to have atleast 64 bits. stackoverflow.com/a/271132/14065
@DavidGrayson LL is in C++11 so there's nothing non-portable in it. In fact LL, UINT64_C and uint64_t all were introduced since C++11, so if you can use int64_t then you must be able to use LL
@Boann I think the reason is that C-style casts can be either reinterpret_cast, const_cast, or static_cast depending on the types involved. C++-style casts express the intent explicitly.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.