Вы используете глобальные переменные, совершенно без необходимости, делая вашу жизнь сложнее, чем она должна быть. Нам, людям, очень трудно отследить, где изменяются глобальные переменные, особенно когда у вас есть несколько глобальных переменных с очень похожими именами.
Итак, вместо того, чтобы пытаться разгадать все это, давайте перепишем его, используя параметры функции, без каких-либо глобальных переменных.
Сначала мы сообщаем библиотеке 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()
, который принимает одно или несколько имен файлов в качестве параметров командной строки и описывает их. Мне нравится начинать с основного, проверяя количество аргументов командной строки и, если указано, первый аргумент. Если нет, или первым является -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
относится к значению, на которое указывает указатель; и изменяется на , чтобы были видимы для вызывающей стороны
Короче говоря, когда мы хотим, чтобы функция могла изменять значение переменной, мы используем указатель. Вот как работает С.
Теперь, когда у нас есть время в эпохе 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 сбрасывает вывод на каждой новой строке, но наличие явного сброса также напоминает нам программистам-людям, что мы хотим, чтобы все до сих пор печаталось, на самом деле появиться на выходе программы стандартным в этой точке. (Таким образом, если один из файлов находится в какой-либо медленной файловой системе, например, на старой USB-карте или в сетевом ресурсе, информация о предыдущих файлах будет отображена до того, как программа получит доступ к медленному файлу. По сути, «остановка» произойдет при ожидаемом место для пользователей.)
Вероятно, стоит упомянуть relatime
и другие связанные параметры монтирования на этом этапе. Проще говоря, это означает, что во избежание количества операций записи на носитель из-за доступа для чтения время доступа не всегда обновляется. Таким образом, если вы не видите, что он меняется даже после чтения файла (например, с помощью cat FILENAME >/dev/null
), это просто означает, что в вашей системе включены параметры монтирования, которые сокращают время доступа к обновлениям, чтобы ускорить доступ к вашей файловой системе и уменьшить количество пишет в него. Это хороший вариант; Я использую это.
Наконец, большинство файловых систем Linux вообще не имеют созданной метки времени. Поля st_ctime
(и st_ctim.tv_sec
и st_ctim.tv_nsec
) относятся к последнее изменение статуса отметка времени. Он отслеживает изменения владельца, группы, прав доступа и количества жестких ссылок.
Когда вы изучаете приведенный выше код, особенно предложения if
, полезно помнить, что в C логическая операция ИЛИ, ||
, имеет короткое замыкание: сначала проверяется левая сторона, но если она не получается, правая сторона вообще не оценивается. Итак, если у вас есть, например, int x = 1, y = 0;
и вы делаете (x == 0 || ++y)
, y
не будет увеличиваться вообще. Я использую это при рассмотрении argv[1]
в самом первом предложении if
в main()
.