计算机系统第二次讨论课题4
选题四
题目
在Unix等操作系统中,计时方式是以\(1970\)年\(1\)月\(1\)日 \(00:00:00\)为基准,按秒为单位进行增减,并采用一个32位的\(int\)型整数来存储这个值。这会导致在手机上设置日期时,无法将日期设置成\(2038\)年\(1\)月\(19\)日之后的日期。
如下图所示,小米、华为只能将时间设置为\(2037\)年\(12\)月\(31\)日,苹果的\(iOS\)也只能多两天。

解释
其实我们通过读题就已经清楚地知道了这背后的原因----由于时间是以\(1970\)年\(1\)月\(1\)日 \(00:00:00\)为基准,按秒为单位进行增减,而这个增减的秒是由一个\(int\)整形来存储的,但是我们都知道\(int\)的存储是有范围的:
当这个相对于\(1970\)年\(1\)月\(1\)日 \(00:00:00\)的增加量\(C\)增加到2147483647的时候,再次增加就会使得结果与我们所想要的值不一样。即这样的结果会溢出:
//created by LIN
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
int main()
{
int a = INT_MAX;
int b = a + 1;
cout << to_string(b);//由于在大多数C++编译器中较大的数会用科学计数法表示 所以转换为字符串输出
}//(演示)
这也就是为什么我们的电子设备的时间不能设置为\(2038\)年\(1\)月\(19\)日之后的日期的原因,因为在这一天的某个时间点,我们的手机时间会被迫重新回到\(1970\)年\(1\)月\(1\)日!
解决方案
在20世纪的时候,由于计算机作为新兴学科的到来,人们由于成本等因素无法考虑那么远(资本总是追求短期的急速利益hhh),所以就会用\(int\)来存储这个增量,谁知道光阴似箭,\(2038\)年离我们貌似也不是那么的遥远。
我们提出以下几种方案来解决这个问题:
1. \(int \rightarrow unsigned\ long\ long\)
2.通过循环取模
3.检查寄存器的溢出位
1. \(int \rightarrow unsigned\ long\ long\)
如果我们将存储的变量的由\(int\)变为\(unsigned\ long\ long\),由于\(unsigned\ long\ long\)的范围是
所以我们可以保证在范围这么大的数保护下溢出的那一天不会那么快的到来~,不过这貌似有了点当初设计\(unix\)内核的大师的一样的想法,所以我们还需要找到不是那么的依赖于范围的方法。
2.通过循环取模
其实这是来自于算法思想的,问题可以建模为如何简单检测\(int\)的溢出?设置一个\(MOD\)即可,每次取模就可以保证不溢出。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int MOD = INT_MAX;
int main()
{
int C;
C %= MOD;
/*
* if C == INT_MAX, then C will be back to 0
* Then we can recount from now on.
* */
return 0;
}
但是这样会有一个缺点是,每次秒数自增的时候,都会要进行依次取模运算,站在长期的视角来看,这样的方法并不可取,因为我们需要每时每刻都调用这个被封装的函数,会使得内核的效率降低。也就是时间要用在刀刃上啦~
3. 检查寄存器的溢出位
是否还记得CPU中的标志位寄存器有对应的位来记录当前运算是否溢出?由于操作系统是硬件和软件之间一种桥梁,我们可以方便的调用硬件的某些接口来检测是否溢出。但是这与方法2是一样的,每次加减都需要检测使得内核的工作量倍增。
现行的解决方法
大家不妨先猜猜现行的操作系统对这个时间方面做了哪些优化呢。
在我们的电脑中我们可以查看一下现在最多表示到的时间点

很显然,只能表示到2119年,相对于1970年这个节点,大概是150年的跨度,由于1亿秒是3年零6个月,也就是3.5年,对比一下可以发现,在内核中把表示这个增量\(C\)的变量换成了\(unsigned\ int\),果然资本都是只注重眼前利益~~

浙公网安备 33010602011771号