C语言文件读写中的文件指针定位问题
问题描述
许多C语言开发者在进行文件操作时会遇到这样的困惑:成功创建文件并写入内容后,立即尝试读取文件内容,却发现无法读取到任何数据。以下是一个典型的错误示例代码:
c
#include <stdio.h>
#define SIZE 256
int main() {
FILE *fptr = fopen("mem.txt","w+"); // 创建/覆盖可读写文件流
char buffer[SIZE + 1]; // 额外存储空字符
buffer[SIZE] = '\0';
if(!fptr) { // 文件打开失败时fptr为NULL
printf("Failed creation");
return -1;
}
printf("Creation successful\n");
fprintf(fptr, "Hello, World!"); // 写入文件
fread(buffer, sizeof(buffer), 1, fptr); // 立即尝试读取
printf("%s", buffer);
fclose(fptr);
return 0;
}
期望输出:
Creation successful
Hello, World!
实际输出:
Creation successful
问题原因
文件指针定位问题
关键问题
在C语言标准中,输出操作后不能直接进行输入操作,除非先调用文件定位函数或刷新缓冲区。
根据C标准(7.23.5.3p7):
输出后不能直接进行输入,除非调用
fflush
函数或文件定位函数(fseek
、fsetpos
或rewind
);输入后也不能直接进行输出,除非调用文件定位函数或输入操作遇到文件结束标志。
写入操作后,文件指针位于文件末尾,此时直接调用fread
会从文件末尾开始读取,自然无法获取已写入的内容。
fread使用问题
c
// fread原型:
size_t fread(void * restrict ptr, size_t size, size_t nmemb,
FILE * restrict stream);
// 错误用法:
fread(buffer, sizeof(buffer), 1, fptr);
这种用法存在问题:
- 请求读取1个
sizeof(buffer)
大小的元素,很可能返回0 - 如果读取的部分元素不完整,其表示形式是不确定的
- 读取的数据不保证以空字符结尾,不能直接作为字符串使用
解决方案
方案1:使用文件定位函数(推荐)
在写入和读取操作之间添加文件定位函数:
c
fprintf(fptr, "Hello, World!"); // 写入文件
rewind(fptr); // 将文件指针重置到文件开头
// 或者使用:fseek(fptr, 0, SEEK_SET);
fread(buffer, sizeof(buffer), 1, fptr); // 读取文件
方案2:正确的fread使用方法
c
size_t length = fread(buffer, 1, sizeof(buffer) - 1, fptr);
buffer[length] = '\0';
printf("%s", buffer);
c
size_t length = fread(buffer, 1, sizeof(buffer), fptr);
printf("%.*s", (int) length, buffer);
方案3:使用fgets替代fread
对于文本文件,使用fgets
可以简化操作,因为它会自动添加空字符:
c
if (fgets(buffer, sizeof buffer, fptr)) {
printf("%s", buffer);
}
方案4:使用fwrite进行输出
如果使用fread
读取,可以使用fwrite
输出,这样可以正确处理文件中的空字符:
c
size_t length = fread(buffer, 1, sizeof(buffer), fptr);
fwrite(buffer, 1, length, stdout);
方案5:使用POSIX系统调用(非便携)
在POSIX系统(Linux、macOS等)中,可以使用pread
和pwrite
系统调用:
c
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
const int fd = open("mem.txt", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1) {
perror("open");
return -1;
}
const char text[] = "Hello, World!";
pwrite(fd, text, sizeof(text) - 1, 0); // 写入文件
char buffer[sizeof(text)];
ssize_t readSize = pread(fd, buffer, sizeof(buffer) - 1, 0); // 读取文件
buffer[readSize] = 0;
puts(buffer);
close(fd);
}
注意
系统调用没有进程级缓冲,每次文件访问都会导致系统调用,性能可能较低。
最佳实践总结
- 始终检查文件操作返回值:确保读写操作成功执行
- 正确处理文件指针定位:在读写操作切换时使用
rewind
、fseek
或fflush
- 安全处理字符串:确保读取的数据正确以空字符结尾,或使用安全的输出方法
- 考虑使用更简单的函数:对于文本文件,
fgets
和fputs
可能比fread
和fwrite
更合适 - 错误处理:使用
perror
输出详细的错误信息,帮助调试
完整修正代码
c
#include <stdio.h>
#define SIZE 256
int main() {
FILE *fptr = fopen("mem.txt","w+");
char buffer[SIZE];
if(!fptr) {
printf("Failed creation");
return -1;
}
printf("Creation successful\n");
fprintf(fptr, "Hello, World!");
rewind(fptr); // 重置文件指针到开头
size_t length = fread(buffer, 1, sizeof(buffer) - 1, fptr);
buffer[length] = '\0';
printf("%s", buffer);
fclose(fptr);
return 0;
}
遵循这些准则,您可以避免常见的文件操作陷阱,编写出健壮可靠的C语言文件I/O代码。