在chrono中处理溢出

编程语言 2026-07-10

It is well known that it is easy for <chrono> to silently overflow under the hood when pushing the boundaries of its range.

众所周知,当把它的取值范围推到边界时,<chrono> 很容易在底层悄悄溢出。

For example:

using namespace std::chrono;
years y{300};
nanoseconds x{300};
std::cout << (y > x) << '\n';

The output is typically

0

though this is actually undefined behavior. The output falsely indicates that 300ns is longer than 300 years.

例如:

using namespace std::chrono;
years y{300};
nanoseconds x{300};
std::cout << (y > x) << '\n';

输出通常是

0

尽管这实际上属于未定义行为。该输出错误地表示300纳秒比300年还长。

This happens because to do the comparison <chrono> first attempts to convert 300 years to nanoseconds, which overflows the underlying 64 bit signed integer used to represent 300 years worth of nanoseconds.

这是因为在进行比较 <chrono> 时,先尝试将300年转换为纳秒,这会导致用于表示300年纳秒数的底层64位带符号整数溢出。

The usual advice is to manually ensure that your chrono arithmetic does not approach overflow boundaries such as the nanoseconds +/- 292 year range.

通常的建议是手动确保你的chrono计算不会接近溢出边界,例如纳秒范围在+/- 292年之间。

Is there a way to easily automate this checking in modern C++, at least for debug builds?

是否有办法在现代C++中轻松自动化地进行这种检查,至少在调试构建中?

解决方案

Is there a way to easily automate this checking in modern C++, at least for debug builds?

Yes.

是的。

The <chrono> durations (and time_points) can use class types for the underlying representation as opposed to integral (or floating point) types. For example, they can use a "safe int" type that turns overflow into a noisy run-time error.

<chrono> durations(以及 time_points)可以使用类类型作为底层表示,而不是整型(或浮点型)。例如,它们可以使用一种“安全整型”类型,将溢出转化为一个嘈杂的运行时错误。

One free, open-source, header-only example of such a class is my bbi integer library.1 This library can be used to easily define your own custom chrono types that will terminate on overflow.

一个自由、开源、头文件独立的示例类是我开发的 bbi整数库。1这个库可以用来轻松定义你自己的自定义chrono类型,并在溢出时会 terminate

For example:

#include "bbi.h"
#include <chrono>
#include <iostream>

namespace mychrono
{

using namespace bbi::term;

using nanoseconds  = std::chrono::duration<i64, std::nano>;
using microseconds = std::chrono::duration<i64, std::micro>;
using milliseconds = std::chrono::duration<i64, std::milli>;
using seconds      = std::chrono::duration<i64>;
using minutes      = std::chrono::duration<i32, std::ratio<60>>;
using hours        = std::chrono::duration<i32, std::ratio<3'600>>;
using days         = std::chrono::duration<i32, std::ratio<86'400>>;
using weeks        = std::chrono::duration<i32, std::ratio<604'800>>;
using months       = std::chrono::duration<i32, std::ratio<2'629'746>>;
using years        = std::chrono::duration<i32, std::ratio<31'556'952>>;

}  // namespace mychrono

int
main()
{
    using namespace mychrono;
    years y{300};
    nanoseconds x{300};
    std::cout << (y > x) << '\n';
}

This requires C++20 or later, and on recent versions of MSVC, gcc and clang, will output the message:

Z<Signed, 64, Terminate>{300) * Z<Signed, 64, Terminate>{31556952000000000) overflowed

and then call std::terminate.

例如:

#include "bbi.h"
#include <chrono>
#include <iostream>

namespace mychrono
{

using namespace bbi::term;

using nanoseconds  = std::chrono::duration<i64, std::nano>;
using microseconds = std::chrono::duration<i64, std::micro>;
using milliseconds = std::chrono::duration<i64, std::milli>;
using seconds      = std::chrono::duration<i64>;
using minutes      = std::chrono::duration<i32, std::ratio<60>>;
using hours        = std::chrono::duration<i32, std::ratio<3'600>>;
using days         = std::chrono::duration<i32, std::ratio<86'400>>;
using weeks        = std::chrono::duration<i32, std::ratio<604'800>>;
using months       = std::chrono::duration<i32, std::ratio<2'629'746>>;
using years        = std::chrono::duration<i32, std::ratio<31'556'952>>;

}  // namespace mychrono

int
main()
{
    using namespace mychrono;
    years y{300};
    nanoseconds x{300};
    std::cout << (y > x) << '\n';
}

这需要C++20或更高版本,在MSVC、GCC和 Clang的较新版本上,会输出以下信息:

Z<Signed, 64, Terminate>{300) * Z<Signed, 64, Terminate>{31556952000000000) overflowed

然后调用 std::terminate

Working demo for all three major platforms.

Working demo 可用于所有三大平台。

The only changes to the code are:

对代码的唯一修改是:

  • #include "bbi.h"
  • Type aliases for the chrono types under your own namespace
  • Change using namespace std::chrono to using namespace mychrono (or manage the namespace change however you prefer).

  • #include "bbi.h"

  • 在你自己的命名空间下为chrono类型定义的类型别名
  • using namespace std::chrono 修改为 using namespace mychrono(或按你偏好的方式管理命名空间的变更)

Once you have debugged your code and want to return to the full efficiency of a release build, simply change:

一旦完成调试,想要回到发布构建的全性能,只需将:

using namespace bbi::term;

to:

改为:

using namespace bbi::raw;

This change redefines your custom chrono types to use built-in signed integers of the indicated bit width for their representation.

这一改动将重新定义你的自定义chrono类型,使其使用所指定位宽的内置带符号整数来表示。


If you would like to throw an exception on overflow, instead of call std::terminate, then change:

如果你希望在溢出时抛出异常,而不是调用 std::terminate,请将:

using namespace bbi::term;

to:

改为:

using namespace bbi::thrw;

Then, on overflow a std::overflow_error will be thrown with a what() message identical to the one printed out previously with terminate().

然后,在溢出时将抛出一个 std::overflow_error,其消息与之前通过 terminate() 打印出的 what() 消息完全相同。


To experiment with saturated arithmetic, change:

要尝试饱和算术,将:

using namespace bbi::term;

to:

改为:

using namespace bbi::sat;

Now, this program outputs the correct answer:

现在,这个程序输出正确的答案:

1

300 years is greater than 300ns.

300年比300纳秒大。


Another way to use this library to get the correct answer instead of overflow is to increase the range of nanoseconds to +/- far beyond the age of the universe. This can be done by having nanoseconds represented by a signed 128-bit type, instead of a 64-bit type. Change:

另一种使用这个库来获得正确答案、而不是溢出的方法,是将 nanoseconds 的取值范围扩大到远超宇宙年龄的+/- 范围。这可以通过让 nanoseconds 使用一个带符号的128位类型来表示,而不是64位类型来实现。将:

using nanoseconds  = std::chrono::duration<i64, std::nano>;

to:

改为:

using nanoseconds  = std::chrono::duration<i128, std::nano>;

Now, this program outputs the correct answer:

现在,这个程序输出正确的答案:

1

300 years is greater than 300ns.

300年比300纳秒大。

You can no longer have a zero-overhead release build with this setting, as there is no portable 128-bit type (until we get a portable _BitInt(128)), but you can remove the overflow checks by changing:

在这个设置下,你将再也无法拥有零开销的发布构建,因为没有可移植的128位类型(直到我们得到一个可移植的 _BitInt(128)),但你可以通过将以下改为来移除溢出检查:

using namespace bbi::term;

to:

改为:

using namespace bbi::wrap;

Working C++20 demo.

Working C++20演示


1 I am the main author of bbi. I am not seeking any financial benefit from this library. I consider helping people with C++ in general, and chrono in particular, a hobby of mine.

1我是 bbi 的主要作者。我并不为这个库寻求任何经济利益。我把帮助人们学习C++,特别是chrono,视为我的爱好。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章