我在eclipse上使用linux上的g ++。我正在制作一个代码来获取文件的时间和输出文件的月,小时等......
调试时,time1
的值意外发生了变化,但我对此问题一无所知。
这段代码有什么问题?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
struct stat stat1, stat2;
struct tm *time1, *time2;
void filestat1(void);
void filestat2(void);
void filetime1(void);
void filetime2(void);
void datecmp(void);
void timecmp(void);
int main(void)
{
filestat1();
filestat2();
filetime1();
filetime2();
datecmp();
timecmp();
}
void filestat1(void)
{
// check if there is no text1
int check = 0;
check = stat("text1", &stat1);
if(check != 0)
{
printf("Error : there is no text1\n");
}
return;
}
void filestat2(void)
{
// check if there is no text2
int check = 0;
check = stat("text2", &stat2);
if(check != 0)
{
printf("Error : there is no text2\n");
}
return;
}
void filetime1(void)
{
time1 = localtime(&stat1.st_mtime); //!!! this change unexpectedly
return;
}
void filetime2(void)
{
time2 = localtime(&stat2.st_mtime);
return;
}
void datecmp(void)
{
printf("date compare\n");
// compare tm_mon
if(time1->tm_mon > time2->tm_mon)
printf("time1 is early \n");
else if(time1->tm_mon < time2->tm_mon)
printf("time2 is early \n");
else{
// compare tm_mday
if(time1->tm_mday > time2->tm_mday)
printf("time1 is early \n");
else if(time1->tm_mday < time2->tm_mday)
printf("time2 is early \n");
// same date
else
printf("same time \n");
}
printf("\n");
}
void timecmp(void)
{
printf(time1->tm_hour);
printf(time2->tm_hour);
printf("time compare\n");
// compare hour
if(time1->tm_hour > time2->tm_hour)
printf("time1 is early \n");
else if(time1->tm_hour < time2->tm_hour)
printf("time2 is early \n");
else{
// compare minutes
if(time1->tm_min > time2->tm_min)
printf("time1 is early \n");
else if(time1->tm_min < time2->tm_min)
printf("time2 is early \n");
// same time
else
printf("same time \n");
}
}
答案 0 :(得分:1)
localtime
返回指向静态结构的指针。您需要在再次调用localtime
之前复制结果。
我会将time1
和time2
声明为结构而不是存储值的指针。
struct tm time1, time2;
void filetime1(void)
{
struct tm *tmp = localtime(&stat1.st_mtime);
if (tmp == NULL) {
//... handle error
}
time1 = *tmp;
}
同样适用于filetime2
。
如果您正在编写多线程代码,则使用函数的可重入变体localtime_r
会更安全。在这种情况下,您将指针传递给结果的结构。
void filetime1(void)
{
struct tm *tmp = localtime_r(&stat1.st_mtime, &time1);
if (tmp == NULL) {
//... handle error
} else {
assert(tmp == &time1);
}
}
答案 1 :(得分:0)
您正在使用全局变量,完全不必要地使您的生活变得更加艰难。我们人类很难跟踪修改全局变量的位置,尤其是当你有几个名称非常相似的全局变量时。
所以,不要试图解开所有这些,而是让我们使用函数参数重写它,而不需要任何全局变量。
首先,我们告诉C库我们想要POSIX.1-2008功能,并包含公开我们所需功能的标头:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <limits.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
接下来,让我们定义一个函数,该函数将文件名作为参数,并指向函数可以存储上次访问和最后修改时间戳的位置。如果功能成功,它将返回0;否则,它会返回-1并设置errno
以指示错误。
int filetime(const char *path,
time_t *accessed, long *accessed_nsec,
time_t *modified, long *modified_nsec)
{
struct stat info;
/* Path must not be NULL or empty. */
if (!path || !path[0]) {
errno = EINVAL;
return -1;
}
/* Get file statistics. */
if (stat(path, &info) == -1)
return -1; /* errno was set by stat() */
/* Save timestamps. */
if (accessed)
*accessed = info.st_atim.tv_sec;
if (accessed_nsec)
*accessed_nsec = info.st_atim.tv_nsec;
if (modified)
*modified = info.st_mtim.tv_sec;
if (modified_nsec)
*modified_nsec = info.st_mtim.tv_nsec;
/* Success. */
return 0;
}
让我们继续编写一个简单的main()
,它将一个或多个文件名作为命令行参数,并对其进行描述。我喜欢通过检查命令行参数的数量来启动main,如果指定,则查看第一个参数。如果没有,或者第一个是-h
或--help
,我想打印该实用程序的用法。这样,我可以将我的示例程序保存在他们自己的目录中,并找到一个我正在寻找的程序,我可以只执行每个没有参数的程序,看看他们每个人做了什么。它比阅读资源要快得多!
int main(int argc, char *argv[])
{
int arg;
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s FILENAME ...\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program will print the last access and\n");
fprintf(stderr, "last modification timestamps for each of\n");
fprintf(stderr, "the specified files.\n");
fprintf(stderr, "\n");
return EXIT_FAILURE;
}
因为至少有一个,但可能不止一个文件名参数,我们在循环中处理它们中的每一个:
for (arg = 1; arg < argc; arg++) {
time_t accessed, modified;
long accessed_ns, modified_ns;
struct tm accessed_localtime, modified_localtime;
在循环中,我们首先调用filetime()
函数。注意我们如何声明上面要填充的变量,以及我们如何调用函数:&accessed
产生指向accessed
的指针。
if (filetime(argv[arg], &accessed, &accessed_ns,
&modified, &modified_ns)) {
/* Nonzero return value, so an error occurred! */
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(errno));
return EXIT_FAILURE;
}
在C中,函数参数按值传递,而不是通过引用传递。这意味着如果函数参数为int foo
,则函数中对foo
的任何更改都只在函数内可见;调用者看不到更改。当我们传递一个指向变量的指针时,比如int *foo
,那么对foo
的更改仍然只在函数中可见,但*foo
指的是指针指向的值;并且的更改对调用者可见。
简而言之,当我们希望函数能够修改变量的值时,我们使用指针。这就是C的工作原理。
既然我们在Unix纪元时间(time_t
)中有时间,我们希望将它们分成本地时间字段:
if (!localtime_r(&accessed, &accessed_localtime) ||
!localtime_r(&modified, &modified_localtime)) {
fprintf(stderr, "%s: Cannot compute timestamps in local time: %s.\n", argv[arg], strerror(errno));
return EXIT_FAILURE;
}
请注意,我再次使用了POSIX.1-2008函数localtime_r()
。在教程中,您经常会看到使用较旧的localtime()
,但是可以在内部使用全局变量(它总是可以返回指向同一结构的指针,为每次调用重用它); localtime_r()
更好。
查看localtime_r
的第二个参数如何也是指针(struct tm
)?同样,这就是你如何以一种对调用者可见的方式改变某些值的函数。
此外,localtime_r()
(或localtime()
)很少会失败,因此很多人都会忽略检查错误。没有任何借口,因为它只需要几行代码,并且如果在某些时候确实发生了错误,那么用户将非常满意清晰的错误代码,而不仅仅是看到程序由于分段错误而崩溃。
剩下的就是打印出所收集的信息。我喜欢使用ISO 8601国际标准的变体来表示时间格式;特别是,即使按字母顺序排序,它也按适当的时间顺序排序。 (我的变体是我喜欢在日期和时间之间使用空格而不是T
。)
printf("%s:\n", argv[arg]); /* The file name or path */
printf(" Modified: %04d-%02d-%02d %02d:%02d:%02d.%03d\n",
modified_localtime.tm_year + 1900,
modified_localtime.tm_mon + 1,
modified_localtime.tm_mday,
modified_localtime.tm_hour,
modified_localtime.tm_min,
modified_localtime.tm_sec,
modified_ns / 1000000L);
printf(" Accessed: %04d-%02d-%02d %02d:%02d:%02d.%03d\n",
accessed_localtime.tm_year + 1900,
accessed_localtime.tm_mon + 1,
accessed_localtime.tm_mday,
accessed_localtime.tm_hour,
accessed_localtime.tm_min,
accessed_localtime.tm_sec,
accessed_ns / 1000000L);
/* Make sure everything written to stdout
is actually written to standard output right now. */
fflush(stdout);
}
return EXIT_SUCCESS;
}
fflush(stdout)
告诉C库确保对stdout
的所有先前写入实际写入标准输出。 (默认情况下,stdout
被缓冲,而stderr
是无缓冲的。)通常,C库将刷新每个换行符的输出,但是在那里有明确的flush也提醒我们人类程序员我们想要一切到目前为止打印,实际上出现在该点的程序标准输出上。 (这样,如果其中一个文件位于某个慢速文件系统上,比如旧的USB记忆棒或网络共享,则在程序访问慢速文件之前会显示先前文件的信息。基本上,&#34;停止&#34;将发生在人类用户的预期位置。)
此时提及relatime
和其他相关的挂载选项可能是个好主意。简单来说,这意味着为了避免由于读访问而对存储介质的写入次数,访问时间并不总是更新。因此,如果您在阅读文件后仍未看到它发生变化(例如使用cat FILENAME >/dev/null
),则表示您的系统启用了挂载选项,可减少访问时间更新,从而加快文件系统访问速度,减少对它的写入次数。这是一个很好的选择;我用它。
最后,大多数Linux文件系统根本没有创建的时间戳。 st_ctime
(以及st_ctim.tv_sec
和st_ctim.tv_nsec
)字段指的是上次状态更改时间戳。它跟踪对所有者,组,权限和硬链接数量的更改。
当你检查上面的代码,特别是if
子句时,记住在C中,逻辑OR运算||
是短路的是有用的:首先评估左侧,但如果失败,则根本不评估右侧。所以,如果你有例如int x = 1, y = 0;
(x == 0 || ++y)
y
,argv[1]
根本不会增加。我在if
的第一个main()
子句中检查{{1}}时使用此功能。