C语言dup、fork操作文件描述符后写入内容被覆盖、换行丢失问题
问题根源分析
你遇到的内容覆盖、字符丢失,是两个C语言文件IO开发中常见的机制误解叠加导致的,和C语言是否忽略换行符没有关系。
核心机制1:共享文件偏移的语义
内核中,由一次open()调用创建的打开文件描述,会被所有通过dup()复制、fork()继承得到的文件描述符共享。所有指向这个打开文件描述的fd,没有独立的读写偏移量,内核只会维护一份统一的偏移值:
- 不管哪个fd执行
read()/write()操作,都会直接修改这个共享偏移,其他所有关联fd的后续读写操作,都会从修改后的偏移位置开始执行。
对应你的代码流程:
- 第一次
open("myfile", O_RDWR|O_CREAT, 0666)后,写入5字节的haha\n,关闭fd后文件总长度为5字节。 - 第二次
open("myfile", O_RDWR)得到的fd,是后续所有文件操作的源头。通过两次dup()操作,分别把0号(标准输入stdin)、1号(标准输出stdout)绑定到这个fd对应的文件描述上;fork()创建的子进程会完整继承所有关联的fd,共享同一个文件偏移。 - 子进程调用
scanf("%s", s)是从0号fd读文件内容,读操作会把共享偏移从文件开头(0位置)移动到文件末尾(因为文件总共只有5字节内容)。 - 子进程的
printf("hello\n")是通过1号fd写文件,由于共享偏移的特性,写操作会从当前偏移(也就是5的位置,原文件内容的末尾)开始,不会从文件开头覆盖原有内容。
核心机制2:stdio全缓冲与fork的冲突
当stdout被重定向到普通磁盘文件(不是终端设备)时,C标准库的stdio会自动把stdout的缓冲模式从默认的行缓冲切换为全缓冲:
- 所有通过
printf()写入的内容,不会立刻调用系统调用写到内核的文件里,而是先存在进程自己的用户态内存缓冲区中,直到缓冲区满、主动调用fflush()、或者进程正常退出的时候,才会真正把缓冲区内容通过write()系统调用写到内核。 fork()会完整复制父进程的整个用户地址空间,包括所有stdio流的状态、内存缓冲区。这意味着父子进程各自持有一份完全独立的stdout缓冲区,两个进程之间没有任何内容同步机制。- 子进程运行时把
hello\n存在自己的缓冲区里,退出时触发flush,从当前共享偏移5的位置开始写入6字节内容,写完后共享偏移移动到11的位置。 - 父进程
wait()到子进程退出后,把Father finished\n存在自己的stdout缓冲区里,退出时触发flush,从当前共享偏移11的位置开始写入自己的内容。
- 子进程运行时把
字符丢失的直接原因
你观察到的“替换printf("hello\n")为printf("hellos")后末尾s丢失”、以及实际文件中hello后换行丢失的现象,本质是:
- 子进程flush缓冲区时,写入的6个字节(无论是
hello\n还是hellos),都是从偏移5的位置开始写的,写完后内核的共享偏移停在11的位置。 - 由于stdio缓冲区是用户态独立的,子进程flush的操作不会主动告知父进程“已经写完6个字节”。当父进程开始flush自己的stdout缓冲区时,由于fork时继承的stdio初始状态没有同步子进程的写入行为,会出现单字节的偏移错位——父进程写入的第一个字符刚好落在子进程写入的最后一个字节的位置,直接覆盖了该位置的内容,也就是你看到的换行符、或者末尾的
s丢失。
内容的提问来源于stack exchange,提问作者user15728444
相关产品推荐
相关产品推荐

