2147483647,是 32 位有符号整数能装下的最大值。把它当成从 1970 年 1 月 1 日零时整开始数的秒数,换算成日历恰好落在 2038 年 1 月 19 日格林尼治时间 3 点 14 分 07 秒。这一秒过去,计数器本该跳到 2147483648,可 32 位装不下,补码运算把它变成 −2147483648——也就是 1901 年 12 月 13 日 20 点 45 分 52 秒。系统不会蓝屏停机,只会安静地把时间倒回一个多世纪前,然后所有依赖「现在晚于过去」的逻辑一起出错。要明白为什么偏偏是这两个日期,得从记账方式说起。
起点为什么是 1970
Unix 于 1969 年在贝尔实验室诞生,给文件记时间总得选一个起点。工程师们选了「离当下足够近、数字足够整」的 1970 年 1 月 1 日 00:00 UTC,没有天文或历史上的特殊含义,纯粹是顺手划的一条线,副作用是此后一切 Unix 时间都长得像「从六十年代末开始数秒」。C 语言标准库把计时类型定名为 time_t,在 32 位机器上它就是个 int——32 位、带一个符号位,雷从这一刻埋下。
2038 那一秒是怎么排出来的
算一遍就知道日子不是背来的。32 位有符号整数的上限是
2³¹ − 1 = 2 147 483 647 秒
一天 86 400 秒,除得 24 855 天余 11 447 秒;从 1970 年 1 月 1 日往后数 24 855 天是 2038 年 1 月 19 日,余下的 11 447 秒是 3 小时 14 分 07 秒,合起来正是那个著名的时刻,换成北京时间是上午 11 点 14 分 07 秒。往回同理:最小值 −2³¹ 对应 1901 年 12 月 13 日 20 点 45 分 52 秒,这是所有 32 位秒数计数器的下界——它同时也是一些老牌文件系统能表示的最早时间。
溢出不是归零,是符号位翻脸
补码表示里最高位是符号位。2 147 483 647 的二进制是 31 个 1,再加 1,进位把最高位挤成 1、低 31 位全部归 0,按有符号解释就是 −2 147 483 648。所以不存在「时间变成 1970」的浪漫归零,而是瞬移回 1901。后果全是连锁的:文件的修改时间比创建时间早了一百多年;证书有效期校验发现 notAfter 落在「过去」而批量报错;定时任务的条件 ctime 比较永远不满足或永远满足;日志时间戳排序错乱,事故复盘无从下手。任何做「当前时间减一下」运算的代码,都会拿到一个荒谬的负数。
谁会在 2038 年真的疼
主流桌面和服务端操作系统已陆续把 time_t 换成 64 位,真正的风险区在看不见的地方。其一是嵌入式与工控:车载控制器、医疗设备、产线 PLC 的固件跑二三十年不更新是常态,它们的「寿命」大概率跨过 2038。其二是存储格式:ext2、ext3 的 inode 里修改时间是 32 位无符号数,能撑到 2106 年(那是另一个「2106 问题」),ext4 则预留了额外标志位把上限推到 2446 年。其三是数据库:MySQL 的 TIMESTAMP 类型早年用 4 字节存储,范围止步 2038,后来版本改为 64 位存储但仍限制取值。顺带一提,NTP 从 1900 年起算的 32 位秒数在 2036 年 2 月 7 日就会先翻一轮 era,比 2038 还早两年。
小结:64 位买来的余裕与迁移的难点
换成 64 位有符号秒,能数到约 9.2×10¹⁸ 秒,除以每年 3 156 万秒,得 2 900 多亿年——宇宙年龄的二十倍,这段宇宙史内高枕无忧。难点不在数值而在迁移:磁盘上的时间字段是 32 位的固定布局,新旧宽度不能原地打补丁;结构体大小一变,所有依赖它的二进制接口都要重编;32 位处理器做 64 位算术还有实打实的性能代价。所以 2038 更像一场持续十几年的管道更换,越冷门的设备管道埋得越深。想直观感受这条时间线,打开本站 /timestamp 页,输入 2147483647 看它对应的时刻,再输入 −1,答案就摆在眼前。