Search code examples
c++c++11c++-chrono

Can std::chrono's time_point silently overflow?


#include <iostream>
#include <chrono>

int main()
{
  constexpr std::chrono::duration<float> kLastWarningMsgDuration{ 10 };

  // default time point (zero)
  std::chrono::steady_clock::time_point lastWarningMsgTime1;
  // minimum time point (some negative value)
  std::chrono::steady_clock::time_point lastWarningMsgTime2 = std::chrono::steady_clock::time_point::min();
  // current time point
  std::chrono::steady_clock::time_point lastWarningMsgTime3 = std::chrono::steady_clock::now();

  bool result1 = std::chrono::steady_clock::now() - lastWarningMsgTime1 >= kLastWarningMsgDuration;
  bool result2 = std::chrono::steady_clock::now() - lastWarningMsgTime2 >= kLastWarningMsgDuration;
  bool result3 = std::chrono::steady_clock::now() - lastWarningMsgTime3 >= kLastWarningMsgDuration;

  std::cout << result1 << std::endl;  // true, more than 10 seconds
  std::cout << result2 << std::endl;  // false ???
  std::cout << result3 << std::endl;  // false, less than 10 seconds
}

I didn't dive too deep into this but I guess the problem is that the second result overflows over what the poor 32-bit float can hold. I could live with this discovery however there's one thing which got stuck in my head and it is this presentantion's bold statement:

If you make a reasonable change that doesn't involve explicit type conversion syntax (and it compiles), you can have confidence that you have not introduced a bug.

Well, this confidence has been quite shaken for me now. Did I misunderstand what time_point::min() does or that statement is not entirely true?


Solution

  • It's not float that's overflowing (that'd be a very large number), std::chrono::steady_clock::now() - lastWarningMsgTime2 is taking some presumably smallish positive integer and subtracting the maximum sized negative integer. Assuming std::chrono::steady_clock uses int64_t you're essentially doing:

    int64_t lastWarningMsgTime2 = std::numeric_limits<int64_t>::min();
    int64_t now = 12345;
    int64_t diff = now - lastWarningMsgTime2;
    

    This obviously overflows, as int64_t is signed this is undefined behaviour (gcc gives a large negative number as the result https://godbolt.org/z/rPYM6oj75).

    Most std::chrono implementations use signed 64-bit values in their durations, even if those durations are nanoseconds that gives you a range of nearly 600 years, therefore in normal usage the statement you've quoted would be correct. In normal usage subtracting two timestamps wouldn't overflow but yes there is no checking for overflows in std::chrono.

    There are obviously still pitfalls, subtracting time_point::min() is one of those pitfalls but I can't see a reason why you'd normally want to do that.