20、文件属性获取:使用 stat 函数获取文件大小、修改时间等属性

咱们写 C 程序的时候,经常需要跟文件打交道。读写文件是基本功,但有时候你还得知道这个文件到底多大、什么时候改的、是谁的。这些信息,就是文件的「属性」。

我刚开始做嵌入式开发那会儿,有个需求是要监控日志文件的大小,超过 10MB 就自动备份。当时我第一反应是打开文件,读到末尾算字节数。后来被老同事一句话点醒了:「你直接用 stat 啊,一行代码的事。」嗯,从那以后我就记住了,stat 是获取文件属性的标准手段

stat 函数是什么

stat 是 POSIX 标准下的系统调用。它的作用很简单:给定一个文件路径,返回这个文件的各种元信息。这些信息包括:

  • 文件大小(字节数)
  • 最后修改时间
  • 最后访问时间
  • 文件权限(读、写、执行)
  • 文件类型(普通文件、目录、符号链接等)
  • 所属用户 ID 和组 ID
  • 硬链接数

说白了,你平时在终端里用 ls -l 看到的那一堆信息,底层就是调用了 stat。

函数原型与头文件

使用 stat 之前,记得包含头文件:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

函数原型长这样:

int stat(const char *path, struct stat *buf);

参数很简单:

  • path:文件路径,字符串
  • buf:指向 struct stat 结构体的指针,函数会把结果填进去

返回值:成功返回 0,失败返回 -1 并设置 errno。

struct stat 结构体

这个结构体是核心。我挑几个最常用的字段说一下:

字段 类型 说明
st_size off_t 文件大小,单位字节
st_mode mode_t 文件类型 + 权限位
st_mtime time_t 最后修改时间
st_atime time_t 最后访问时间
st_ctime time_t 状态改变时间(注意不是创建时间)
st_uid uid_t 文件所有者的用户 ID
st_gid gid_t 文件所属组 ID
st_nlink nlink_t 硬链接数量
小提示:st_ctime 不是创建时间,是「状态改变时间」。比如你改了文件权限,st_ctime 就会更新。很多新手在这里踩过坑,包括我。

完整示例:获取文件大小和修改时间

光说不练假把式。咱们直接写一段代码,看看怎么用 stat 拿到文件大小和最后修改时间:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char *argv[]) {
    if (argc < 2) {
        fprintf(stderr, "用法: %s <文件名>\n", argv[0]);
        return 1;
    }

    struct stat file_stat;
    if (stat(argv[1], &file_stat) == -1) {
        perror("stat 失败");
        return 1;
    }

    // 文件大小
    printf("文件大小: %lld 字节\n", (long long)file_stat.st_size);

    // 修改时间
    char time_buf[64];
    struct tm *tm_info = localtime(&file_stat.st_mtime);
    strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", tm_info);
    printf("最后修改时间: %s\n", time_buf);

    // 文件权限(简单打印八进制)
    printf("文件权限: %o\n", file_stat.st_mode & 0777);

    return 0;
}

编译运行:

gcc -o file_stat file_stat.c
./file_stat test.txt

输出大概长这样:

文件大小: 1024 字节
最后修改时间: 2025-03-15 14:30:22
文件权限: 644

判断文件类型

st_mode 字段里不光有权限,还藏着文件类型。标准库提供了一组宏来判断:

  • S_ISREG(m) — 普通文件
  • S_ISDIR(m) — 目录
  • S_ISLNK(m) — 符号链接
  • S_ISCHR(m) — 字符设备
  • S_ISBLK(m) — 块设备
  • S_ISFIFO(m) — 命名管道
  • S_ISSOCK(m) — 套接字

举个例子:

if (S_ISREG(file_stat.st_mode)) {
    printf("这是一个普通文件\n");
} else if (S_ISDIR(file_stat.st_mode)) {
    printf("这是一个目录\n");
}
注意:stat 会跟随符号链接。如果你想知道符号链接本身的属性,要用 lstat 而不是 stat。我曾经在写备份脚本时没注意这个,结果统计的全是目标文件的大小,而不是链接文件本身。

知识体系图

下面这张图帮你理清 stat 相关的核心逻辑:

stat 函数知识体系 stat(path, &buf) struct stat 结构体 st_size → 文件大小 st_mtime / st_atime / st_ctime st_mode → 权限 + 类型 单位:字节 (off_t) 用 localtime() 转成可读格式 S_ISREG / S_ISDIR 等宏判断 注意:符号链接请用 lstat 替代 stat

避坑指南

我这些年用 stat 踩过几个坑,分享给你:

  • 文件不存在时 stat 会返回 -1。别假设文件一定存在,记得检查返回值。
  • st_size 的类型是 off_t,在 32 位系统上可能只有 4 字节,大文件会溢出。打印时建议强转成 long long
  • 时间字段是 time_t,也就是从 1970 年 1 月 1 日开始的秒数。直接打印是看不懂的,必须用 localtimegmtime 转换。
  • 权限判断别自己算位运算,用 S_IRUSRS_IWUSR 这些宏更清晰。
核心总结:stat 是获取文件属性的标准接口。记住三个关键字段:st_size(大小)、st_mtime(修改时间)、st_mode(类型+权限)。配合 S_ISREG 等宏和 localtime 函数,你就能轻松拿到文件的各种信息。
公众号:蓝海资料掘金营,微信 deep3321