父进程在malloc之后进行了fork操作... 子进程需要释放它吗?

18

你脑海中的问题答案:是的,这是为学校而做的。不,我不能使用线程来解决这个问题。是的,我寻找了答案,有些人说“是”,有些人说“不是”。我还在核实我的教授,因为我不希望因为别人要求这个问题“修复”而不公平地失去分数。

说了这么多,考虑一下Linux系统上的C语言简单程序。我申请了一些内存,然后进行了fork操作。我将我的项目简化到了一个确切的问题:

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

int main( void )
{
    char * args [] = { "someinvalidcommand", NULL };

    // malloc before the fork (happens in parent process)
    char * something = (char *)malloc(sizeof(char));

    pid_t child_pid = fork();

    // are there now two things that need to be freed:
    // one for each process?

    if(child_pid == 0) // child process
    {
        //free(something); // is this needed?

        // execvp (it won't return if succeeded)
        if(execvp(args[0], args) < 0)
        {
            // or do I only need to free it here?
            printf("%s: No such file or directory\n", args[0]);
            /*
             * EDIT: Calling it here seems to fix the issue.  It turns out
             * that the system calls were the ones with the "still reachable"
             * errors, so I guess it's not all that important that the
             * memory be freed.
             *
             * free(something)
             */
            _exit(1);
        }
    }
    else // parent process
    {
        int status;
        while(wait(&status) != child_pid);
        if(status != 0)
        {
            printf("command status: %i\n", WEXITSTATUS(status));
        }
    }

    free(something);
    return 0;
}

现在问题有点混乱。据我所知,fork会在特定状态下创建父进程的完全副本(包括文本、数据等)。我在某个地方读到过这包括任何malloc'd内容(即堆)。然而,我在别的地方读到它不包括堆,因为有一个叫做“写时复制”的东西,但是我在别的地方又读到,“写时复制”只是一种透明且无关紧要的优化。但是最有道理的是,既然它是一个COPY,那么它有自己的一切。
但是我记得当使用fork()时,任何被malloc的内容将包含相同的内存地址,所以父进程和子进程指向同一物体吗?我需要在子进程中释放资源吗?只有指针被复制了,还是指针指向的数据也被复制了?
我使用了valgrind,当子进程退出时,它只是抱怨所有的内存仍然可以访问。它“仍然可以访问”是什么意思?事实上,它“仍然可以访问”回答了我的问题,并表示父进程是唯一负责释放内存的。

这个线程解释了,https://dev59.com/um445IYBdhLWcg3w_PG3?rq=1 - M.M
我建议在您的评论或标签中添加您的操作系统。 - M.M
3个回答

8
在没有调用exec家族的情况下,你需要使用free()来释放它。父进程和子进程指向的不是同一个东西,因为它们是独立的进程,不共享相同的地址空间。想象一下在另一个选项下会发生什么,例如如果父进程free()了它,然后子进程试图访问它。
如果你调用了诸如execvp()之类的函数,那么就像tmyklebu所提到的那样,你的进程会被清除,并且你不需要做任何事情。
"仍然可达"意味着你仍然有对它的引用,但你还没有使用free()释放它。由于所有的内存在终止时都将被free(),有时这并不是一个问题,与永久丢失已分配内存相比,这可能更好些。 Valgrind的FAQ本身说“你的程序可能没问题--它没有释放它可以释放的一些内存。这很常见,通常是合理的。” 对此的看法不同,有些人认为显式地free()一切是好的形式,而其他人则认为这是一种浪费资源的无意义行为,因为程序终止会自动做到这一点。

2
这正是我说的。 “父母和孩子指向不同的东西......想象一下在另一种情况下会发生什么。” - Crowman
1
是的,作业的一部分是正确使用malloc和free,这就是我在问的原因。我测试了malloc和_exit的组合,在没有fork的程序中也出现了相同的“仍可访问”错误,所以这可能是我需要担心的问题。 - user3598200
没问题。当 execvp() 清空你的地址空间时,它不再需要其参数的内存了。 - Crowman
1
你永远不需要“释放”任何东西。 - R.. GitHub STOP HELPING ICE
2
如果你想让它们被释放,你必须自己释放它们。问题的背景很明显,孩子需要释放它们吗?还是父母会替孩子释放它们?答案是否定的,父母不会替孩子释放它们,如果你想让它们在孩子身上被释放,孩子必须自己去释放。 - Crowman
显示剩余9条评论

5

execvp函数会清空你的地址空间,所以分配的内存也会消失。

_exit函数会退出你的程序,也就是说分配的内存也会消失。

在子进程中,你不需要显式地调用free函数;事实上(由于COW机制),这样做并不是一个好主意。


你似乎基本上是在说,由于操作系统在 _exit 发生时会自动释放所有内容,因此 OP 不必担心释放东西。 - M.M
1
@MattMcNabb:没错。此外,您不想写入您不必要操作的内存(因为这会产生副本),因此在exec之前释放指针会对可读性和性能造成影响,但没有任何好处。 - tmyklebu
如果您打算在没有exec()的情况下保留子进程一段时间,并且执行与父进程更或多或少独立的操作,该怎么办? - SamB
@SamB:那么每当父进程或子进程第一次写入某个页面时,操作系统都要进行大量复制。这意味着您可能会看到实质性的性能下降。为什么要这样做呢? - tmyklebu

2

在调用execvp之前调用free是没有意义的,而且实际上会使您的代码更难复用/移植,因为如果调用进程是多线程的,则在子进程中调用free是无效的。


嗯?为什么无效? - SamB
1
@SamB:对于一个多线程进程进行fork操作后,在子进程中只能调用异步信号安全函数。调用任何异步信号不安全的函数会导致未定义行为。 - R.. GitHub STOP HELPING ICE
哦,我明白了,POSIX其实已经很简洁地解释了这个原因:在http://pubs.opengroup.org/onlinepubs/9699919799/functions/fork.html上查找“*一个进程应该被创建为单线程。[...]”。 - SamB
@SamB:是的。从概念上讲,由于子进程中的内存来自父进程的某个瞬时状态,其中其他线程可能已经处于异步信号不安全函数的中间状态,而这些线程不再存在以继续前进,因此子进程的环境类似于从中断这些AS-不安全函数的信号处理程序分叉的情况。 - R.. GitHub STOP HELPING ICE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接