使用 read() 和 write() 系统调用(最底层、最灵活)
这是最基本、最高效的方法,因为它直接与内核交互,没有额外的缓冲层开销,它适用于大文件拷贝,并且可以精确控制每次读取和写入的字节数。

(图片来源网络,侵删)
核心思想:
- 打开源文件和目标文件。
- 循环执行以下操作,直到文件末尾: a. 从源文件中读取一块数据到缓冲区。 b. 将缓冲区中的数据写入目标文件。
- 关闭所有打开的文件描述符。
完整代码示例 (copy_syscall.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // 用于 read, write, close
#include <fcntl.h> // 用于 open
#include <errno.h> // 用于 errno
#include <string.h> // 用于 strerror
#define BUFFER_SIZE 4096 // 定义一个缓冲区大小,通常是 4KB,与磁盘块大小对齐
void copy_file(const char *src_path, const char *dest_path) {
int src_fd, dest_fd;
ssize_t bytes_read, bytes_written;
char buffer[BUFFER_SIZE];
// 1. 打开源文件 (只读)
src_fd = open(src_path, O_RDONLY);
if (src_fd == -1) {
fprintf(stderr, "Error opening source file '%s': %s\n", src_path, strerror(errno));
exit(EXIT_FAILURE);
}
// 2. 打开目标文件 (只写,如果文件不存在则创建,如果存在则清空)
// O_CREAT: 如果文件不存在则创建
// O_WRONLY: 只写
// O_TRUNC: 如果文件已存在,则将其长度截断为0
// 0644: 文件权限,所有者读写,组和其他用户只读
dest_fd = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dest_fd == -1) {
fprintf(stderr, "Error opening destination file '%s': %s\n", dest_path, strerror(errno));
close(src_fd); // 确保关闭已打开的源文件
exit(EXIT_FAILURE);
}
// 3. 循环读取和写入
while ((bytes_read = read(src_fd, buffer, BUFFER_SIZE)) > 0) {
bytes_written = write(dest_fd, buffer, bytes_read);
if (bytes_written != bytes_read) {
// 写入的字节数不等于读取的字节数,说明出错
fprintf(stderr, "Error writing to destination file.\n");
close(src_fd);
close(dest_fd);
exit(EXIT_FAILURE);
}
}
// 4. 检查 read 是否出错 (返回 -1)
if (bytes_read == -1) {
fprintf(stderr, "Error reading from source file: %s\n", strerror(errno));
close(src_fd);
close(dest_fd);
exit(EXIT_FAILURE);
}
// 5. 关闭文件描述符
close(src_fd);
close(dest_fd);
printf("File copied successfully from '%s' to '%s'\n", src_path, dest_path);
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
return EXIT_FAILURE;
}
copy_file(argv[1], argv[2]);
return EXIT_SUCCESS;
}
如何编译和运行:
# 编译 gcc -o copy_syscall copy_syscall.c # 运行 (假设有一个名为 a.txt 的源文件) ./copy_syscall a.txt b.txt # 验证 ls -l b.txt cat b.txt
使用 C 标准库 fopen(), fread(), fwrite() (更简单、可移植)
这种方法使用 C 语言标准库,代码更简洁,可移植性更好(可以在其他 Unix-like 系统甚至 Windows 上编译运行),标准库内部通常会使用缓冲,对小文件拷贝来说非常方便。
核心思想:
- 使用
fopen()以二进制模式打开源文件和目标文件。 - 使用
fread()读取数据到缓冲区。 - 使用
fwrite()将缓冲区数据写入目标文件。 - 使用
fclose()关闭文件指针。
完整代码示例 (copy_cstdio.c)
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 4096
void copy_file_cstdio(const char *src_path, const char *dest_path) {
FILE *src_file, *dest_file;
char buffer[BUFFER_SIZE];
size_t bytes_read;
// 1. 以二进制模式 ("rb", "wb") 打开文件,防止特殊字符被解释
src_file = fopen(src_path, "rb");
if (src_file == NULL) {
perror("Error opening source file");
exit(EXIT_FAILURE);
}
dest_file = fopen(dest_path, "wb");
if (dest_file == NULL) {
perror("Error opening destination file");
fclose(src_file); // 确保关闭已打开的源文件
exit(EXIT_FAILURE);
}
// 2. 循环读取和写入
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, src_file)) > 0) {
if (fwrite(buffer, 1, bytes_read, dest_file) != bytes_read) {
perror("Error writing to destination file");
fclose(src_file);
fclose(dest_file);
exit(EXIT_FAILURE);
}
}
// 3. 检查 fread 是否出错
if (ferror(src_file)) {
perror("Error reading from source file");
}
// 4. 关闭文件指针
fclose(src_file);
fclose(dest_file);
printf("File copied successfully using C stdio.\n");
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
return EXIT_FAILURE;
}
copy_file_cstdio(argv[1], argv[2]);
return EXIT_SUCCESS;
}
如何编译和运行:
# 编译 gcc -o copy_cstdio copy_cstdio.c # 运行 ./copy_cstdio a.txt c.txt
使用 mmap() (内存映射文件,性能极高)
对于非常大的文件,mmap() 是一种非常高效的方法,它将文件直接映射到进程的虚拟内存空间,你可以像操作内存一样操作文件,避免了 read/write 的数据拷贝,大大提高了性能。
核心思想:
- 打开源文件和目标文件。
- 获取源文件的大小。
- 使用
mmap()将源文件映射到内存。 - 使用
mmap()为目标文件创建一个映射(通常先用ftruncate()调整目标文件大小)。 - 使用
memcpy()将源内存映射区的内容拷贝到目标内存映射区。 - 使用
munmap()解除映射。
完整代码示例 (copy_mmap.c)
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
void copy_file_mmap(const char *src_path, const char *dest_path) {
int src_fd, dest_fd;
struct stat src_stat;
char *src_map = NULL, *dest_map = NULL;
// 1. 打开源文件
src_fd = open(src_path, O_RDONLY);
if (src_fd == -1) {
fprintf(stderr, "Error opening source file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
// 2. 获取源文件大小
if (fstat(src_fd, &src_stat) == -1) {
fprintf(stderr, "Error getting source file size: %s\n", strerror(errno));
close(src_fd);
exit(EXIT_FAILURE);
}
// 3. 打开/创建目标文件
dest_fd = open(dest_path, O_RDWR | O_CREAT | O_TRUNC, 0644);
if (dest_fd == -1) {
fprintf(stderr, "Error opening destination file: %s\n", strerror(errno));
close(src_fd);
exit(EXIT_FAILURE);
}
// 4. 调整目标文件大小与源文件一致
if (ftruncate(dest_fd, src_stat.st_size) == -1) {
fprintf(stderr, "Error resizing destination file: %s\n", strerror(errno));
close(src_fd);
close(dest_fd);
exit(EXIT_FAILURE);
}
// 5. 映射源文件
src_map = mmap(NULL, src_stat.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0);
if (src_map == MAP_FAILED) {
fprintf(stderr, "Error mapping source file: %s\n", strerror(errno));
close(src_fd);
close(dest_fd);
exit(EXIT_FAILURE);
}
// 6. 映射目标文件
dest_map = mmap(NULL, src_stat.st_size, PROT_WRITE, MAP_SHARED, dest_fd, 0);
if (dest_map == MAP_FAILED) {
fprintf(stderr, "Error mapping destination file: %s\n", strerror(errno));
munmap(src_map, src_stat.st_size);
close(src_fd);
close(dest_fd);
exit(EXIT_FAILURE);
}
// 7. 内存拷贝
memcpy(dest_map, src_map, src_stat.st_size);
// 8. 解除映射
if (munmap(src_map, src_stat.st_size) == -1) {
perror("munmap source");
}
if (munmap(dest_map, src_stat.st_size) == -1) {
perror("munmap destination");
}
// 9. 关闭文件描述符
close(src_fd);
close(dest_fd);
printf("File copied successfully using mmap.\n");
}
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
return EXIT_FAILURE;
}
copy_file_mmap(argv[1], argv[2]);
return EXIT_SUCCESS;
}
直接调用系统命令 system()
这是一种“取巧”的方法,如果你的 C 程序不需要关心拷贝过程的细节,可以直接调用 Linux 的 cp 命令。
核心思想:
使用 system() 函数执行一个字符串命令。

(图片来源网络,侵删)
代码示例 (copy_system.c)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <source_file> <destination_file>\n", argv[0]);
return EXIT_FAILURE;
}
char command[256];
snprintf(command, sizeof(command), "cp %s %s", argv[1], argv[2]);
printf("Executing command: %s\n", command);
int ret = system(command);
if (ret == -1) {
perror("system() call failed");
} else {
printf("Command executed with exit status: %d\n", WEXITSTATUS(ret));
}
return (ret == -1 || WEXITSTATUS(ret) != 0) ? EXIT_FAILURE : EXIT_SUCCESS;
}
缺点:
- 效率低:需要创建一个新的 shell 进程来执行
cp命令,开销很大。 - 不可移植:依赖于
cp命令的存在,在 Windows 上无法工作。 - 控制力弱:无法在 C 代码中获取拷贝过程中的错误信息或进度。
总结与对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
read/write |
效率高,底层灵活,无额外库依赖 | 代码稍复杂,需要手动处理错误和缓冲 | 大文件拷贝、性能敏感场景、需要精确控制I/O |
fread/fwrite |
代码简洁,可移植性好,标准库自带 | 性能略低于系统调用(但有缓冲优化) | 通用文件操作,小到中等文件拷贝,推荐首选 |
mmap |
性能极高,适合超大文件,内存拷贝非常快 | 实现复杂,对文件大小有限制,映射区域需连续 | 超大文件拷贝,内存映射文件操作 |
system("cp") |
实现最简单,一行代码搞定 | 效率极低,不可移植,控制力弱 | 快速实现,不关心性能和可移植性的脚本化任务 |
推荐选择:
- 日常学习和通用开发:使用
fread/fwrite方法,它在代码简洁性和性能之间取得了很好的平衡,是大多数情况下的最佳选择。 - 追求极致性能:如果拷贝的文件非常大(比如几百MB到GB级别),使用
mmap方法可以获得最佳性能。 - 学习底层原理:从
read/write开始,这是理解文件 I/O 的基础。 - 快速实现:如果只是临时用一下,不关心性能,可以用
system("cp")。
