1. C语言文件操作基础认知
1.1 文件的本质与存储原理
在计算机系统中,文件本质上是存储在磁盘上的数据集合。当我们用C语言操作文件时,实际上是在进行内存与磁盘之间的数据交换。这个过程涉及到计算机体系结构中的几个关键概念:
-
内存与磁盘的差异:
- 内存(RAM):速度快但断电后数据丢失
- 磁盘:速度较慢但数据持久化存储
- 文件操作就是在这两者之间建立桥梁
-
数据流的概念:
文件在C语言中被视为数据流(stream),可以想象成一条数据管道。当我们读取文件时,数据从磁盘流向内存;写入文件时,数据从内存流向磁盘。
重要提示:理解这个数据流概念对后续学习文件指针和缓冲区机制至关重要。在实际编程中,所有的文件操作都是围绕这个数据流动过程展开的。
1.2 文本文件与二进制文件的深度解析
文本文件特点:
- 存储ASCII或Unicode字符
- 人类可读,可用文本编辑器直接查看
- 每行以换行符('\n')结束
- 常见扩展名:.txt, .c, .h, .csv
二进制文件特点:
- 直接存储数据的二进制表示
- 存储效率高,读写速度快
- 不可直接阅读,需要特定程序解析
- 常见扩展名:.exe, .jpg, .mp3, .dat
底层存储差异示例:
假设我们要存储整数12345:
- 文本文件:存储为字符'1','2','3','4','5'(5个字节)
- 二进制文件:直接存储为0x3039(2个字节,取决于系统)
c复制// 文本文件写入示例
int num = 12345;
fprintf(fp, "%d", num); // 写入5个字符
// 二进制文件写入示例
fwrite(&num, sizeof(int), 1, fp); // 写入sizeof(int)个字节
1.3 文件指针的底层机制
文件指针(FILE*)是C标准库定义的结构体指针,它封装了以下关键信息:
- 文件描述符(操作系统级别)
- 当前读写位置
- 缓冲区状态
- 错误标志
c复制// FILE结构体的简化表示(实际实现更复杂)
typedef struct {
int fd; // 文件描述符
char* buffer; // 数据缓冲区
size_t pos; // 当前位置
size_t size; // 文件大小
int flags; // 状态标志
} FILE;
经验之谈:每次调用fopen()时,系统会:
- 在堆上分配一个FILE结构体
- 建立与磁盘文件的连接
- 初始化缓冲区
这就是为什么必须调用fclose()来释放这些资源。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 文件基础操作详解
2.1 fopen函数深度解析
fopen()函数的完整原型:
c复制FILE *fopen(const char *filename, const char *mode);
文件打开模式全解析:
| 模式 | 含义 | 文件存在 | 文件不存在 | 初始位置 | 常见用途 |
|---|---|---|---|---|---|
| "r" | 只读 | 打开 | 返回NULL | 文件开始 | 读取已有文件 |
| "w" | 只写 | 清空 | 创建 | 文件开始 | 创建新文件/覆盖旧文件 |
| "a" | 追加 | 打开 | 创建 | 文件末尾 | 日志记录 |
| "r+" | 读写 | 打开 | 返回NULL | 文件开始 | 修改已有文件 |
| "w+" | 读写 | 清空 | 创建 | 文件开始 | 创建可读写文件 |
| "a+" | 读写 | 打开 | 创建 | 文件末尾 | 读取和追加数据 |
路径处理注意事项:
- Windows路径需要使用双反斜杠或单正斜杠:
c复制fp = fopen("C:\\Users\\test.txt", "r"); // 正确 fp = fopen("C:/Users/test.txt", "r"); // 也正确 - 相对路径是相对于程序的工作目录,不是源代码目录
避坑指南:建议使用绝对路径开发,使用相对路径部署。检查路径是否正确的技巧:
c复制if (fp == NULL) { perror("文件打开失败"); // 会打印具体错误原因 exit(EXIT_FAILURE); }
2.2 fclose函数与资源管理
fclose()不仅关闭文件,还完成以下重要工作:
- 刷新缓冲区:确保所有数据写入磁盘
- 释放FILE结构体占用的内存
- 释放文件描述符
c复制// 安全的文件关闭模式
if (fclose(fp) != 0) {
perror("文件关闭失败");
// 处理错误
}
典型错误案例:
c复制FILE* fp1 = fopen("a.txt", "r");
FILE* fp2 = fopen("b.txt", "r");
fclose(fp1);
// 忘记关闭fp2 -> 资源泄露
最佳实践:采用"打开后立即计划关闭"的编程习惯:
c复制FILE* fp = NULL; // 打开文件 fp = fopen("file.txt", "r"); if (fp == NULL) { // 错误处理 } // 立即写关闭语句(可以先注释掉) // fclose(fp); // 中间处理代码... // 最后取消注释关闭语句 fclose(fp);
3. 文本文件读写高级技巧
3.1 字符级操作:fgetc与fputc
性能考虑:
- 单字符读写简单但效率低
- 每次调用都涉及系统调用和磁盘I/O
- 适合处理小文件或特殊需求
c复制// 高效的文件复制实现(字符版)
void copy_file_char(const char* src, const char* dst) {
FILE *fs = fopen(src, "r");
FILE *fd = fopen(dst, "w");
if (!fs || !fd) {
// 错误处理
return;
}
int c;
while ((c = fgetc(fs)) != EOF) {
fputc(c, fd);
}
fclose(fs);
fclose(fd);
}
性能测试:用上述函数复制1MB文件可能需要数百毫秒,而使用缓冲区技术可以降到几毫秒。
3.2 行处理:fgets与fputs的陷阱
fgets的特殊行为:
- 读取到'\n'会停止
- 缓冲区会包含'\n'
- 总是以'\0'结束
- 可能不会读满缓冲区
c复制// 安全的行读取函数
char* safe_fgets(char* buf, int size, FILE* fp) {
char* ret = fgets(buf, size, fp);
if (ret) {
// 去除换行符
buf[strcspn(buf, "\n")] = '\0';
}
return ret;
}
常见问题:
- 缓冲区溢出防护:
c复制char buf[10]; fgets(buf, sizeof(buf), fp); // 安全 // 对比gets(buf)的危险性 - 跨平台换行符差异:
- Windows: "\r\n"
- Unix: "\n"
- Mac旧版: "\r"
3.3 格式化I/O:fscanf与fprintf
格式化字符串的高级用法:
c复制// 读取复杂格式
struct Record {
int id;
char name[50];
double value;
};
fscanf(fp, "%d,%49[^,],%lf", &rec.id, rec.name, &rec.value);
// 写入固定宽度字段
fprintf(fp, "%05d%-20s%8.2f", id, name, value);
安全注意事项:
- 总是检查返回值:
c复制int matched = fscanf(fp, "%d %s", &num, str); if (matched != 2) { // 处理格式错误 } - 字符串宽度限制:
c复制char buf[10]; fscanf(fp, "%9s", buf); // 防止溢出
4. 二进制文件操作精要
4.1 fread与fwrite的底层原理
函数原型深度解析:
c复制size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明:
- ptr: 数据缓冲区
- size: 每个元素的字节数
- nmemb: 要读写的元素个数
- 返回值:成功读写的元素个数(非字节数)
高效读写技巧:
- 单次大块读写优于多次小块读写
- 合理设置缓冲区大小(通常4KB-8KB)
- 内存对齐考虑
c复制// 高效文件复制(二进制版)
#define BUF_SIZE 4096
void copy_file_bin(const char* src, const char* dst) {
FILE *fs = fopen(src, "rb");
FILE *fd = fopen(dst, "wb");
if (!fs || !fd) return;
char buffer[BUF_SIZE];
size_t bytes;
while ((bytes = fread(buffer, 1, BUF_SIZE, fs)) > 0) {
fwrite(buffer, 1, bytes, fd);
}
fclose(fs);
fclose(fd);
}
4.2 结构体文件存储的注意事项
结构体对齐问题:
c复制#pragma pack(push, 1) // 取消结构体对齐
struct Student {
int id;
char name[20];
float score;
// 在32位系统下默认对齐后可能是12字节
// 使用#pragma pack后是28字节
};
#pragma pack(pop)
版本兼容性问题:
- 结构体大小可能随编译器/平台变化
- 解决方案:
- 使用固定大小的数据类型(如int32_t)
- 添加版本标识
- 考虑序列化为文本格式(如JSON)
c复制// 带版本控制的写入
struct Header {
uint32_t magic; // 文件标识 0xCAFEBABE
uint16_t version; // 版本号
uint16_t struct_size; // 结构体大小
};
// 写入时
Header hdr = {0xCAFEBABE, 1, sizeof(Student)};
fwrite(&hdr, sizeof(Header), 1, fp);
5. 高级文件操作技术
5.1 随机访问:fseek与ftell
文件定位函数:
c复制int fseek(FILE *stream, long offset, int whence);
long ftell(FILE *stream);
void rewind(FILE *stream);
whence参数:
- SEEK_SET:文件开头
- SEEK_CUR:当前位置
- SEEK_END:文件末尾
应用示例:
c复制// 获取文件大小
long get_file_size(FILE* fp) {
long orig = ftell(fp); // 保存当前位置
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, orig, SEEK_SET); // 恢复位置
return size;
}
// 读取文件最后1KB
fseek(fp, -1024, SEEK_END);
char buf[1024];
fread(buf, 1, 1024, fp);
5.2 错误处理与状态检测
关键函数:
c复制int feof(FILE *stream); // 是否到达文件末尾
int ferror(FILE *stream); // 是否发生错误
void clearerr(FILE *stream); // 清除错误标志
正确的文件读取模式:
c复制while (1) {
char buf[1024];
size_t n = fread(buf, 1, sizeof(buf), fp);
if (n == 0) {
if (feof(fp)) {
// 正常结束
break;
} else if (ferror(fp)) {
// 处理错误
perror("读取错误");
break;
}
}
// 处理读取到的数据
}
6. 实战:学生管理系统文件存储
6.1 系统设计
功能需求:
- 添加学生记录
- 查询学生记录
- 修改学生记录
- 删除学生记录
- 列表显示所有学生
文件格式设计:
- 二进制文件
- 固定长度记录
- 索引文件加速查询
6.2 核心代码实现
c复制// 学生结构体
typedef struct {
int id; // 4字节
char name[20]; // 20字节
float score; // 4字节
int is_deleted; // 删除标志 4字节
} Student;
// 添加学生
void add_student(FILE* fp, Student* stu) {
fseek(fp, 0, SEEK_END);
fwrite(stu, sizeof(Student), 1, fp);
}
// 按ID查找学生(简单线性搜索)
int find_student(FILE* fp, int id, Student* out) {
rewind(fp);
Student s;
while (fread(&s, sizeof(Student), 1, fp) == 1) {
if (!s.is_deleted && s.id == id) {
*out = s;
return 1; // 找到
}
}
return 0; // 未找到
}
// 更新学生成绩
void update_score(FILE* fp, int id, float new_score) {
Student s;
long pos = 0;
rewind(fp);
while (fread(&s, sizeof(Student), 1, fp) == 1) {
if (!s.is_deleted && s.id == id) {
fseek(fp, pos, SEEK_SET);
s.score = new_score;
fwrite(&s, sizeof(Student), 1, fp);
break;
}
pos = ftell(fp);
}
}
6.3 性能优化技巧
- 索引文件:
- 维护ID到文件位置的映射
- 使用内存缓存热门记录
- 批量操作:
- 收集多个操作后一次性写入
- 文件预分配:
- 预先分配大文件减少碎片
7. 跨平台开发注意事项
7.1 文件路径处理
可移植的路径处理方案:
c复制#if defined(_WIN32)
#define PATH_SEP '\\'
#else
#define PATH_SEP '/'
#endif
// 构造路径
char path[256];
snprintf(path, sizeof(path), "data%cstudents.dat", PATH_SEP);
7.2 文本文件换行符
统一换行符处理:
c复制// 写入时统一为LF
fputs(line, fp);
fputc('\n', fp);
// 读取时处理CRLF
char* normalize_newline(char* str) {
char *p = str;
while (*p) {
if (*p == '\r' && *(p+1) == '\n') {
*p = '\n';
memmove(p+1, p+2, strlen(p+2)+1);
}
p++;
}
return str;
}
7.3 文件锁机制
跨平台文件锁实现:
c复制int lock_file(FILE* fp) {
#ifdef _WIN32
_lock_file(fp);
#else
flock(fileno(fp), LOCK_EX);
#endif
return 0;
}
int unlock_file(FILE* fp) {
#ifdef _WIN32
_unlock_file(fp);
#else
flock(fileno(fp), LOCK_UN);
#endif
return 0;
}
8. 性能调优与测试
8.1 缓冲区设置
自定义缓冲区大小:
c复制#define BUF_SIZE (16*1024) // 16KB
FILE* fp = fopen("large.dat", "rb");
if (fp) {
char buffer[BUF_SIZE];
setvbuf(fp, buffer, _IOFBF, BUF_SIZE);
// 后续操作...
fclose(fp);
}
8.2 读写性能测试
测试不同缓冲区大小的影响:
c复制void test_buffer_size(const char* filename, size_t buf_size) {
char* buf = malloc(buf_size);
FILE* fp = fopen(filename, "rb");
setvbuf(fp, buf, _IOFBF, buf_size);
clock_t start = clock();
char temp;
while (fread(&temp, 1, 1, fp) == 1) {
// 模拟处理
}
clock_t end = clock();
printf("Buffer %5zu bytes: %.2f ms\n",
buf_size, (double)(end-start)*1000/CLOCKS_PER_SEC);
fclose(fp);
free(buf);
}
8.3 内存映射文件
高性能文件I/O方案:
c复制#ifdef _WIN32
// Windows内存映射
HANDLE hFile = CreateFile("data.dat", GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
char* data = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
// 使用data指针访问文件内容...
UnmapViewOfFile(data);
CloseHandle(hMap);
CloseHandle(hFile);
#else
// Unix内存映射
int fd = open("data.dat", O_RDONLY);
struct stat sb;
fstat(fd, &sb);
char* data = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
// 使用data指针访问文件内容...
munmap(data, sb.st_size);
close(fd);
#endif
9. 安全编程实践
9.1 输入验证
安全的文件名处理:
c复制int is_valid_filename(const char* name) {
if (!name || !*name) return 0;
// 禁止目录遍历
if (strstr(name, "..") || strchr(name, '/') || strchr(name, '\\')) {
return 0;
}
// 检查非法字符
const char* invalid = "<>:\"|?*";
while (*invalid) {
if (strchr(name, *invalid)) return 0;
invalid++;
}
return 1;
}
9.2 临时文件安全
安全的临时文件创建:
c复制char* create_temp_file() {
#ifdef _WIN32
char tmp_path[MAX_PATH];
GetTempPathA(MAX_PATH, tmp_path);
char tmp_file[MAX_PATH];
GetTempFileNameA(tmp_path, "tmp", 0, tmp_file);
return strdup(tmp_file);
#else
char pattern[] = "/tmp/tempfileXXXXXX";
int fd = mkstemp(pattern);
if (fd == -1) return NULL;
close(fd);
return strdup(pattern);
#endif
}
9.3 文件权限控制
设置合理文件权限:
c复制void create_secure_file(const char* filename) {
FILE* fp = fopen(filename, "w");
if (!fp) return;
// Unix权限设置
#ifdef __unix__
fchmod(fileno(fp), 0600); // 仅所有者可读写
#endif
// 写入敏感数据...
fclose(fp);
// Windows权限设置
#ifdef _WIN32
_chmod(filename, _S_IREAD | _S_IWRITE);
#endif
}
10. 调试技巧与常见问题
10.1 文件操作调试方法
- 检查errno:
c复制fp = fopen("missing.txt", "r"); if (fp == NULL) { printf("错误代码: %d\n", errno); perror("具体错误"); } - 文件位置检查:
c复制printf("当前位置: %ld\n", ftell(fp)); - 缓冲区状态检查:
c复制fflush(fp); // 强制刷新缓冲区
10.2 典型问题解决方案
问题1:文件内容不全
可能原因:
- 忘记fflush()
- 程序崩溃未正常关闭文件
- 缓冲区未填满
解决方案:
c复制// 关键操作后立即刷新
fprintf(fp, "重要数据");
fflush(fp);
问题2:二进制文件读取错误
可能原因:
- 结构体对齐不一致
- 大小端差异
- 版本不兼容
解决方案:
c复制// 写入时记录结构体大小
size_t struct_size = sizeof(Student);
fwrite(&struct_size, sizeof(size_t), 1, fp);
fwrite(&student, sizeof(Student), 1, fp);
// 读取时检查
size_t saved_size;
fread(&saved_size, sizeof(size_t), 1, fp);
if (saved_size != sizeof(Student)) {
// 处理不兼容情况
}
问题3:文件共享冲突
解决方案:
c复制// 独占方式打开文件
fp = fopen("data.dat", "rb+"); // 读写模式
if (fp) {
lock_file(fp); // 使用前面定义的锁函数
// 执行操作...
unlock_file(fp);
fclose(fp);
}
11. 现代C语言文件操作
11.1 新版C标准特性
C11新增文件操作函数:
c复制// 更安全的版本
errno_t fopen_s(FILE** fp, const char* filename, const char* mode);
size_t fread_s(void* buffer, size_t bufferSize, size_t elementSize,
size_t count, FILE* stream);
11.2 替代方案:内存流
使用内存作为文件操作:
c复制// 创建内存流
char buffer[1024];
FILE* mem = fmemopen(buffer, sizeof(buffer), "w+");
// 像普通文件一样操作
fprintf(mem, "Hello memory!");
fseek(mem, 0, SEEK_SET);
char str[20];
fgets(str, sizeof(str), mem);
printf("从内存流读取: %s\n", str);
fclose(mem);
11.3 面向对象封装
C++风格的文件类封装:
c复制typedef struct {
FILE* fp;
const char* name;
size_t size;
} File;
File* file_open(const char* filename, const char* mode) {
File* f = malloc(sizeof(File));
f->fp = fopen(filename, mode);
if (f->fp) {
f->name = strdup(filename);
fseek(f->fp, 0, SEEK_END);
f->size = ftell(f->fp);
rewind(f->fp);
}
return f;
}
void file_close(File* f) {
if (f) {
if (f->fp) fclose(f->fp);
free((void*)f->name);
free(f);
}
}
12. 项目实战:日志系统实现
12.1 需求分析
功能需求:
- 多级别日志(DEBUG, INFO, WARN, ERROR)
- 自动添加时间戳
- 日志文件滚动(按大小/时间)
- 线程安全
12.2 核心实现
c复制#define LOG_MAX_SIZE (10*1024*1024) // 10MB
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARNING,
LOG_ERROR
} LogLevel;
typedef struct {
FILE* fp;
char filename[256];
size_t current_size;
pthread_mutex_t lock;
} Logger;
void log_write(Logger* log, LogLevel level, const char* message) {
pthread_mutex_lock(&log->lock);
// 检查是否需要滚动文件
if (log->current_size >= LOG_MAX_SIZE) {
fclose(log->fp);
// 备份旧日志
char oldname[256];
time_t now = time(NULL);
struct tm* tm = localtime(&now);
strftime(oldname, sizeof(oldname),
"%Y%m%d_%H%M%S", tm);
char backup[512];
snprintf(backup, sizeof(backup), "%s.%s",
log->filename, oldname);
rename(log->filename, backup);
// 创建新日志
log->fp = fopen(log->filename, "a");
log->current_size = 0;
}
// 添加时间戳和日志级别
time_t now = time(NULL);
char timestamp[64];
strftime(timestamp, sizeof(timestamp),
"%Y-%m-%d %H:%M:%S", localtime(&now));
const char* level_str;
switch (level) {
case LOG_DEBUG: level_str = "DEBUG"; break;
case LOG_INFO: level_str = "INFO"; break;
case LOG_WARNING: level_str = "WARN"; break;
case LOG_ERROR: level_str = "ERROR"; break;
}
// 写入日志
size_t n = fprintf(log->fp, "[%s] %s: %s\n",
timestamp, level_str, message);
log->current_size += n;
fflush(log->fp);
pthread_mutex_unlock(&log->lock);
}
12.3 使用示例
c复制Logger* log = malloc(sizeof(Logger));
strcpy(log->filename, "app.log");
log->fp = fopen(log->filename, "a");
log->current_size = ftell(log->fp);
pthread_mutex_init(&log->lock, NULL);
log_write(log, LOG_INFO, "系统启动");
// ...
log_write(log, LOG_ERROR, "发生错误");
fclose(log->fp);
pthread_mutex_destroy(&log->lock);
free(log);
13. 性能关键型应用优化
13.1 无缓冲I/O
直接使用系统调用:
c复制// Unix风格
int fd = open("data.bin", O_WRONLY | O_CREAT, 0644);
write(fd, buffer, buffer_size);
close(fd);
// Windows风格
HANDLE hFile = CreateFile("data.bin", GENERIC_WRITE,
0, NULL, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, NULL);
WriteFile(hFile, buffer, buffer_size, &bytesWritten, NULL);
CloseHandle(hFile);
13.2 异步I/O
c复制// Linux异步I/O
struct aiocb cb = {
.aio_fildes = fd,
.aio_buf = buffer,
.aio_nbytes = size,
.aio_offset = offset
};
aio_write(&cb);
// 检查完成
while (aio_error(&cb) == EINPROGRESS) {
usleep(1000);
}
13.3 零拷贝技术
使用sendfile系统调用:
c复制// 高效文件传输(Linux)
int in_fd = open("source.bin", O_RDONLY);
int out_fd = open("dest.bin", O_WRONLY | O_CREAT, 0644);
struct stat stat_buf;
fstat(in_fd, &stat_buf);
sendfile(out_fd, in_fd, NULL, stat_buf.st_size);
close(in_fd);
close(out_fd);
14. 跨语言文件交互
14.1 与Python交互
C写入,Python读取:
c复制// C端写入结构化数据
struct Data {
int id;
double value;
char name[32];
};
FILE* fp = fopen("data.bin", "wb");
struct Data d = {123, 45.67, "测试"};
fwrite(&d, sizeof(struct Data), 1, fp);
fclose(fp);
python复制# Python端读取
import struct
with open('data.bin', 'rb') as f:
data = f.read()
# 按照C结构体定义解析
id, value, name = struct.unpack('id32s', data)
name = name.decode('utf-8').rstrip('\x00')
14.2 与Java交互
通过JNI传递文件描述符:
c复制// C端
JNIEXPORT void JNICALL Java_FileProcessor_processFile
(JNIEnv *env, jobject obj, jstring jpath) {
const char* path = (*env)->GetStringUTFChars(env, jpath, NULL);
FILE* fp = fopen(path, "rb");
// 处理文件...
fclose(fp);
(*env)->ReleaseStringUTFChars(env, jpath, path);
}
14.3 通用文本格式
使用CSV格式交互:
c复制// C端生成CSV
void write_csv(FILE* fp, const Data* data, int count) {
fprintf(fp, "id,name,value\n");
for (int i = 0; i < count; i++) {
fprintf(fp, "%d,\"%s\",%.2f\n",
data[i].id, data[i].name, data[i].value);
}
}
15. 文件系统高级主题
15.1 文件监控
Linux inotify示例:
c复制int fd = inotify_init();
int wd = inotify_add_watch(fd, "/path/to/watch",
IN_MODIFY | IN_CREATE | IN_DELETE);
char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event))));
while (1) {
ssize_t len = read(fd, buf, sizeof(buf));
if (len == -1) break;
const struct inotify_event* event;
for (char* ptr = buf; ptr < buf + len;
ptr += sizeof(struct inotify_event) + event->len) {
event = (const struct inotify_event*)ptr;
printf("事件: %s\n", event->name);
}
}
15.2 内存映射高级用法
共享内存映射:
c复制// 创建共享内存文件
int fd = shm_open("/shared_mem", O_CREAT | O_RDWR, 0644);
ftruncate(fd, SIZE);
// 映射内存
void* ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
// 多个进程可以通过ptr访问同一内存区域
15.3 文件属性操作
获取和设置文件属性:
c复制struct stat st;
stat("file.txt", &st);
printf("大小: %ld\n", st.st_size);
printf("最后修改: %s", ctime(&st.st_mtime));
// 修改时间
struct utimbuf new_times = {
.actime = st.st_atime, // 保持访问时间不变
.modtime = time(NULL) // 修改时间设为现在
};
utime("file.txt", &new_times);
16. 嵌入式系统特殊考量
16.1 闪存存储特性
- 擦除块大小(通常4KB-128KB)
- 有限擦写次数(通常10万-100万次)
- 写入前必须擦除
c复制// 闪存友好型写入策略
#define FLASH_BLOCK_SIZE (64*1024)
void flash_write(FILE* fp, const void* data, size_t size) {
static char block[FLASH_BLOCK_SIZE];
static size_t pos = 0;
// 缓冲区足够
if (pos + size <= FLASH_BLOCK_SIZE) {
memcpy(block + pos, data, size);
pos += size;
return;
}
// 缓冲区不足,先写入
fwrite(block, 1, pos, fp);
fflush(fp);
// 开始新块
pos = 0;
memcpy(block + pos, data, size);
pos += size;
}
16.2 掉电保护设计
- 写前日志(Write-ahead logging)
- 原子替换
- 校验和
c复制// 原子文件更新技术
void atomic_update(const char* filename, const void* data, size_t size) {
char tempname[256];
snprintf(tempname, sizeof(tempname), "%s.tmp", filename);
// 写入临时文件
FILE* fp = fopen(tempname, "wb");
fwrite(data, 1, size, fp);
fflush(fp);
fsync(fileno(fp)); // 确保写入物理介质
fclose(fp);
// 原子重命名
rename(tempname, filename);
}
16.3 嵌入式文件系统选择
- LittleFS:针对闪存优化
- SPIFFS:轻量级
- FATFS:兼容性好
c复制// FATFS示例
FRESULT res;
FATFS fs;
FIL fil;
// 挂载文件系统
f_mount(&fs, "", 0);
// 打开文件
res = f_open(&fil, "data.txt", FA_READ);
if (res == FR_OK) {
char buf[100];
UINT br;
f_read(&fil, buf, sizeof(buf), &br);
f_close(&fil);
}
// 卸载文件系统
f_mount(NULL, "", 0);
17. 测试与验证
17.1 单元测试框架
文件操作测试示例:
c复制#include <assert.h>
void test_file_operations() {
// 测试文件创建
FILE* fp = fopen("testfile", "w");
assert(fp != NULL);
assert(fputs("测试数据", fp) >= 0);
assert(fclose(fp) == 0);
// 测试文件读取
fp = fopen("testfile", "r");
assert(fp != NULL);
char buf[20];
assert(fgets(buf, sizeof(buf), fp) != NULL);
assert(strcmp(buf, "测试数据") == 0);
assert(fclose(fp) == 0);
// 清理
remove("testfile");
}
int main() {
test_file_operations();
printf("所有测试通过!\n");
return 0;
}
17.2 模糊测试
文件操作模糊测试:
c复制void fuzz_file_ops() {
const char* patterns[] = {
"normal.txt",
"with space.txt",
"very_long_name_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.txt",
NULL
};
for (int
