fork vs execvp,子进程中内存泄露?

2020-03-14 23:09:40 +08:00
 52coder

突然被老弟问了一个问题,有点蒙蔽,怎么说我也写过 1 年 C 呀,哈哈哈 持续征集C 短小精悍开源代码 完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/wait.h>

int
main(int argc, char *argv[])
{
    int rc = fork();
    if (rc < 0) {
        // fork failed; exit
        fprintf(stderr, "fork failed\n");
        exit(1);
    } else if (rc == 0) {
        // child: redirect standard output to a file
        close(STDOUT_FILENO); 
        open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);

        // now exec "wc"...
        char *myargs[3];
        myargs[0] = strdup("wc");   // program: "wc" (word count)
        myargs[1] = strdup("p4.c"); // argument: file to count
        myargs[2] = NULL;           // marks end of array
        execvp(myargs[0], myargs);  // runs word count
    } else {
        // parent goes down this path (original process)
        int wc = wait(NULL);
    assert(wc >= 0);
    }
    return 0;
}

子进程里使用了 strdup,没看到内存释放的地方,即使 valgrind 开启了--trace-children=yes 也没有检测到内存泄露,这里是否存在内存泄露?之前有看过 execvp 会替换 fork 之后的子进程的内存空间?不清楚这块内存关系是怎么样的?

我和老弟争执一点如下,我认为如果程序快速执行完退出,不必太过纠结资源释放问题,进程退出了,系统会回收资源,当然好的习惯是不用的时候去 free 掉。针对常驻进程,需要重点关注内存泄露问题。 我认为下面的程序没有内存泄露,即使 valgrind 检测出来:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    int *p = (int *)malloc(10 *sizeof(int));
    printf("program exit\n");
    
    return 0;
}
1938 次点击
所在节点    程序员
9 条回复
codehz
2020-03-14 23:33:44 +08:00
程序结束是保证能释放所占用的普通内存的(除了 hugepage 和共享内存)
如果观察到使用内存增多,那么肯定是因为系统缓存了一些东西
fork 到 exec 之间的这段时间所有内存分配都可以简单的无视,不需要考虑释放,exec 之后全都会被吃掉,valgrind 无需跟踪这部分的“泄漏”,除非 exec 失败,通过 exit 退出,这样 valgrind 才能跟踪到泄漏
52coder
2020-03-14 23:43:00 +08:00
@codehz 单就这个例子而言:myargs[0] = “ wc”改成这样就不纠结了,我也有点搞不清楚 fork 之后子进程里的机制,execvp 失败退出,避免走到其它不该走的逻辑。例子这里有点不严谨。execvp 成功了不返回,失败了还是要回来,这个函数有点不厚道呀。
ysc3839
2020-03-14 23:45:31 +08:00
> 我认为如果程序快速执行完退出,不必太过纠结资源释放问题,进程退出了,系统会回收资源

可以认为是对的,比如 Windows 下许多 GUI 程序在启动时会加载图标之类的资源,直到退出也不会释放。包括微软 VS 向导创建的代码也是这样的。
lance6716
2020-03-14 23:46:38 +08:00
当然是用 gdb 看一下喽
geelaw
2020-03-15 03:49:29 +08:00
如果惟一可能的释放点是进程结束之前,那么是没有必要进行这个操作的,因为“大厦马上就要拆除了,没有必要打扫房间”。

不过你的第一个问题可以用简单的逻辑论证为什么你不需要释放:如果你释放了 strdup 产生的内存,则无法正确调用 execvp (除非你准备静态存储用来放置参数,但这显然无端增加麻烦,系统不会这样设计),因此你无法释放这段内存。

第二个问题取决于你的对内存泄露的定义。
msg7086
2020-03-15 05:50:34 +08:00
如果你的程序需要继续运行下去,那么你可以在 execvp 后释放 strdup 出来的空间。
execvp 执行成功以后你的进程就被替换了,内存当然全没了。所以最多也只要考虑执行失败的情况。
52coder
2020-03-15 10:40:26 +08:00
@msg7086 执行成功以后进程被替换?执行时需要的内存从哪来的?有介绍这方面的文章吗?我觉得应该是你说的这个点。
msg7086
2020-03-15 11:30:53 +08:00
@52coder exec 不就是把自身进程替换成目标程序吗。当前进程的代码和内存空间都会被抹平,然后开始执行新的程序,让新的程序来重新申请内存空间。
比如你这里运行了 exec("wc")以后,你程序的代码和内存都会消失,然后内核把 wc 的代码加载进内存,然后运行 wc 的入口,让 wc 去申请和使用它自己新的内存空间。
52coder
2020-03-15 15:04:15 +08:00
@msg7086 学习了,多谢大佬

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/652865

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX