前言
友情提示:如果对产生覆盖的原因和过程不感兴趣的同学,可以直接跳到解决方案。
函数背景
Ctime
函数常被用于获得当前的具体时间,其函数声明如下:
声明
char *ctime(const time_t *timer);
返回值
根据该函数的声明,该函数应该返回一个表示当前日期的字符串
(char*)
但是,事实上该函数返回的是一个static char
的静态数组的首地址。这也是为什么在程序中一旦使用两次ctime
函数之后,一起打印输出的永远都是第二次的时间。
参数
参数方面,不是本次讲解的重点,百度有很多,这里不做说明。
其他说明
此处附上《C和指针》一书中的原话,“标准并未提及存储这个字符串的内存类型,许多编译器使用一个静态数组。因此,下一次调用Ctime时,这个字符串将被覆盖。因此,如果你需要保存它的值,应该事先为其复制一份。”
覆盖过程的讲解
我们有时需要在代码中的某个位置使用Ctime函数,用于当时的时间显示,可能在过一段时间后,又需要对当前时间进行输出。此时我们就很容易的使用到Ctime函数,所以我们会有以下代码。
示例代码
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<string.h>
int main(void)
{
char* time1;
char* time2;
time_t t1;
time_t t2;
/*time函数返回的是一个double类型的以秒为单位,
从1970年1月1日00:00:00到当前相距的秒数.*/
t1 = time(NULL);
time1 = ctime(&t1);
getchar();
t2 = time(NULL);
time2 = ctime(&t2);
}
逐步运行
说明:此时程序执行到t1 = time(NULL);
,此时t1
变量的值显示为1585313523
而time1
的还是一个垃圾值,还并未对该变量进行赋值。程序往下继续进行。
说明:接上一步,此时程序已经完成赋值,time2
已经成功接收到ctime
函数返回的日期字符串。到此我们已经完成了对ctime函数的第一次调用,如果此时对其进行输出,显然会输出如上图Fri Mar 27 20:52:03 2020\n
。程序继续往下执行正在等待getchar()
的字符输入。注意此处time1变量的地址是0x008ad7b
。程序继续。
说明:当手动输入任意字符后,getchar
函数执行结束,程序到达t2 = time(NULL)
。随后完成了对t2
的赋值,我们可以看到,两次的时间差值为1296s。(这说明我刚才写着两段花去了1296s)。下面一步就是重头戏了,请注意此时time2
还未进行赋值,所以它也是个垃圾值。接下来的一步就是对其进行赋值。程序继续。
矛盾出现
当对time2
赋值后,发现,其地址也变成了和time1
一样的地址,根据ctime
函数的声明,我们知道该函数返回的是一个char*
类型,此处就是Fri Mar 27 20:52:03 2020\n
这样的字符串,我们都知道不同的字符串那么系统分配的内存肯定是不同的,但是此时time2
的地址居然和一个之前被分配过内存的time1
变量的地址一样,这是不符合常理的,已经分配过的内存在释放前是不可能再进行一次分配的,只有一种可能就是给time1,time2
变量赋值的是同一个地址(char*
接收字符类型的地址),但是根据函数的声明,返回的是一个char
类型的地址没错,但是前后两次的字符串面向是不同的,第一一次是Fri Mar 27 20:52:03 2020\n
,第二次是Fri Mar 27 21:13:39 2020\n
,大家都知道不同的字符串地址肯定是不同的,而此处,根据前面的推理,time1,time2
接收的应该是同一个地址。这便产生了矛盾。
回到主旨句
此时我们回想到前文中说过的,ctime
函数返回的是一个静态数组的地址。而并不是字符串本身,字符串是存储在这个静态数组中的。
打比方
每次调用ctime
函数时,ctime
要上交一个东西给接收它返回值的变量(time1,time2)
,但是ctime
函数不直接上交,它首先将东西放到一个箱子里上(static
数组),然后告诉time1,time2
,,这个箱子的位置(static
数组的地址),让它们俩根据这个地址去箱子里取东西,而且这个箱子一次只能放一样东西,第一次放的东西会被第二次放的东西挡住(覆盖)。所以。无论是time1,time2
它们所拿到的东西都是最后一次放进去的。所以无论是time1
还是time2
,他们得到的都是同一个箱子的地址,所以第二次有人再往里放东西,由于time1
之前并没有将第一次放进去的东西保存(用字符数组存储)或者进行处理(打印输出之类),所以它隔段时间再去看这个箱子的时候,之前的东西看不见了(被覆盖),只有第二次放进去的东西了。至此,我们解释了ctime
函数这个由于函数声明的误导引起的一些错误情况。
解决方案
解决思想
获取后马上对得到的字符放到字符数组中进行保存。
注意:
是字符数组,而不是字符指针,原因是本来就是用char*
类型的指针进行接收,现在将接收到的地址用另一个字符指针存储,后果是一样的,还是得到静态数组的地址,相当于,你告诉了第三个人那个箱子的位置,但是箱子里面的东西你还是没有拿出来。所以只能用字符数组进行拷贝存储。
具体方案
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<time.h>
#include<string.h>
int main(void)
{
char* time1;
char* time2;
time_t t1;
time_t t2;
char ret1[50];
char ret2[50];
/*time函数返回的是一个double类型的以秒为单位,
从1970年1月1日00:00:00到当前相距的秒数.*/
t1 = time(NULL);
time1 = ctime(&t1);
sprintf(ret1, "%s", time1);
getchar();
t2 = time(NULL);
time2 = ctime(&t2);
sprintf(ret2, "%s", time1);
printf("第一次使用ctime得到时间:%s\n", ret1);
printf("第二次使用ctime得到时间:%s\n", ret2);
}
说明:使用sprintf
函数可以实现将一个字符串从字符指针中直接存储到一个自己定义的字符数组中,转换格式为"%s"
。当然也可以使用其他方式将静态数组中的内容读取到一个字符数组中保存起来,只是使用sprintf
函数是我目前认为最简单的方式。记得要包含相应的头文件。
个人总结:
问题是一个很简单的问题,但是由于事先被其函数声明所迷惑,也让我在项目代码调试过程浪费了很多时间,在逐步接近真相的过程中,发现网上很多说明会产生覆盖情况,但是没有人具体阐述过,虽然有解决思路,但是解决的具体方案还是很少有人提及。所以,写下这篇博客,只是希望当有人和自己产生一样困惑时能得到一些提示。