fork和(failed)exec后C文件指针改变

fnc*_*ers 5 c linux fork file

我做了制作叉子的程序,我认为孩子不影响父母.

但是文件指针已更改,但我没有对父进行任何更改.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(void) {
    FILE *fp = fopen("sm.c", "r");
    char buf[1000];
    char *args[] = {"invailid_command", NULL};

    fgets(buf, sizeof(buf), fp);
    printf("I'm one %d %ld\n", getpid(), ftell(fp));
    if (fork() == 0) {
        execvp(args[0], args);
        exit(EXIT_FAILURE);
    }
    wait(NULL);
    printf("I'm two %d %ld\n", getpid(), ftell(fp));
} 
Run Code Online (Sandbox Code Playgroud)

这输出

I'm one 21500 20
I'm two 21500 -1
Run Code Online (Sandbox Code Playgroud)

我想让两个printf调用之间的文件指针不变.

为什么文件指针会改变,即使execvp失败也可以使文件指针不变?

Joh*_*ger 6

感谢 Jonathan Leffler 为我们指明了正确的方向。

尽管您的程序在 CentOS 7 / GCC 4.8.5 / GLIBC 2.17 上不会对我产生相同的意外行为,但您观察到不同的行为是合理的。根据 POSIX(您依赖 POSIX ),您的程序的行为实际上是未定义的fork。以下是相关部分的一些摘录(强调):

可以通过文件描述符访问打开的文件描述,该文件描述符使用open()或等函数创建pipe(),或通过流访问,该流使用fopen()或等函数创建popen()。文件描述符或流被称为它所引用的打开文件描述的“句柄”;一个打开的文件描述可能有多个句柄。

[...]

涉及任何一个句柄(“活动句柄”)的函数调用结果在本 POSIX.1-2017 卷中的其他地方定义,但如果使用两个或多个句柄,并且其中任何一个是流,则应用程序应确保他们的行动如下所述进行协调。如果不这样做,结果是 undefined

[...]

为了使句柄成为活动句柄,应用程序应确保在上次使用句柄(当前活动句柄)和第一次使用第二个句柄(未来活动句柄)之间执行以下操作。然后第二个句柄成为活动句柄。[...]

要应用这些规则,句柄不必在同一进程中。

请注意,在 a 之后fork(),在之前存在的地方存在两个句柄。应用程序应确保,如果两个句柄都可以访问,则它们都处于另一个可以首先成为活动句柄的状态。[在符合前述条件的情况下,]申请应准备好fork() 如同更改主动句柄一样。(如果其中一个进程执行的唯一操作是 exec 函数之一或 _exit()(not exit()),则在该进程中永远不会访问句柄。

对于第一个手柄,以下第一个适用条件适用。[令人印象深刻的一长串替代方案,不适用于 OP 的情况......]

  • 如果流以允许读取的模式打开,并且底层打开的文件描述指的是能够查找的设备,则应用程序应执行fflush(),或应关闭流。

对于第二个手柄:

  • 如果任何先前的活动句柄已被显式更改文件偏移量的函数使用,除了上面对第一个句柄的要求外,应用程序应在适当的位置执行lseek()fseek()(根据句柄类型)。

因此,为了让 OP 的程序在父级和子级中访问相同的流,POSIX 要求fflush() stdin在分叉之前父级,以及fseek()在启动后子级。然后,在等待子进程终止后,父进程必须fseek()流。鉴于我们知道孩子的 exec 将失败,但是,可以通过让孩子使用_exit()(不访问流)而不是exit().

遵守 POSIX 的规定会产生以下结果:

当遵循这些规则时,无论使用的句柄顺序如何,实现都应确保应用程序,即使是由多个进程组成的应用程序,都应产生正确的结果:写入时不会丢失或重复数据,并且所有数据都应写入订单,除非寻求。

不过值得注意的是,

它是实现定义的,是否以及在什么条件下,所有输入都只看到一次。


我明白,仅仅听到您对程序行为的期望不符合相关标准的说法可能会有些不满意,但这确实是全部。父进程和子进程确实有一些以公共打开文件描述形式存在的相关共享数据(它们具有关联的单独句柄),这似乎是意外(和未定义)行为的载体,但没有预测您看到的特定行为的基础,也不是我看到的同一程序的不同行为。