最可控的二进制拷贝方式是 std::ifstream + std::ofstream 配合 read()/write() 以 binary 模式分块读写,并检查 gcount();mmap 可零拷贝但需预分配大小;std::filesystem::copy() 简便但性能与行为不跨平台一致。

用 std::ifstream + std::ofstream 配合 read()/write() 是最可控的二进制拷贝方式
直接读写原始字节,不经过字符串转换或格式解析,避免编码、换行符、BOM 等干扰。适用于任意文件类型(图片、视频、可执行文件等)。
常见错误是用 operator>> 或 getline() 读取——它们会停在空格或换行,且自动跳过空白,根本读不全二进制内容。
- 务必以
std::ios::binary模式打开输入和输出流,否则 Windows 下可能把\r\n当作单个\n处理,导致长度错乱 - 推荐分块读写(如 64KB),避免一次性
read()整个大文件触发内存暴涨或分配失败 - 读完后检查
gcount(),它返回实际读取字节数;不要只依赖eof(),因为 EOF 只在尝试读超尾后才置位
std::ifstream src("a.bin", std::ios::binary);
std::ofstream dst("b.bin", std::ios::binary);
char buf[65536];
while (src.read(buf, sizeof(buf))) {
dst.write(buf, src.gcount());
}
dst.write(buf, src.gcount()); // 最后一次不足整块的
mmap 在 Linux/macOS 上做零拷贝文件复制更高效,但 Windows 要用 CreateFileMapping
当文件大于百 MB 且内存充足时,mmap 绕过用户态缓冲区,让内核直接映射磁盘页到进程地址空间,memcpy 就是拷贝地址,实际延迟到 page fault 才真正读盘。
但它不是万能加速:小文件反而因映射/解映射开销变慢;同时写入目标文件时若没预分配大小,频繁扩展会导致大量 fsync 或碎片写入。
立即学习“C++免费学习笔记(深入)”;
- Linux 下用
open()+mmap()+memcpy()+msync(),注意PROT_WRITE和MAP_SHARED对目标文件生效 - Windows 下对应的是
CreateFile()→CreateFileMapping()→MapViewOfFile(),不能直接用mmap - 目标文件必须提前
ftruncate()(Linux)或SetFilePointer()+SetEndOfFile()(Windows)设好大小,否则写越界会静默失败
用 std::filesystem::copy() 最省事,但别指望它快或跨平台行为一致
C++17 引入的 std::filesystem::copy() 是封装好的高层接口,内部可能调用系统命令(如 Linux 的 cp)、syscall 或回退到流拷贝,具体取决于标准库实现(libstdc++、libc++、MSVC STL 行为不同)。
它适合“能跑通就行”的场景,比如构建脚本里复制资源文件;但不适合性能敏感路径,也不该用于判断是否“真的用了零拷贝”。
- 默认行为是
copy_options::none,遇到同名目标会失败;加copy_options::overwrite_existing才覆盖 - 某些实现(如旧版 MSVC)对大文件仍走流式拷贝,没用
CopyFileEx的异步能力 - 不抛异常时返回值是
void,没法知道底层是不是调了sendfile或copy_file_range
别忽略 sync()、fsync() 和缓存策略的影响
即使 memcpy 完成、流 close 成功,数据可能还卡在内核页缓存里,断电或 crash 就丢。要不要刷盘,取决于你对“完成”的定义:是“写入内核缓冲区”,还是“落盘可靠”。
多数场景下,用户要的是后者——尤其配置文件、数据库日志这类关键数据。
- Linux 下,在
close()前对输出 fd 调用fsync();或者用O_SYNC标志打开文件(但会显著拖慢写速) - Windows 下对应的是
FlushFileBuffers(),必须在CloseHandle()前调用 -
std::ofstream的flush()只刷 C++ 流缓冲区,不触发内核刷盘;它和fsync完全不是一回事
真正难的从来不是“怎么拷”,而是“什么时候才算拷完了”。缓存层级、文件系统策略、硬件写缓存开关……这些地方一漏,测试时好好的,上线就丢数据。



















