execvp 的子进程内存释放问题
Child process memory free problem with execvp
以下代码来自"Operating Systems: Three Easy Pieces"一书。代码让我很困惑。
我知道 execvp
永远不会 returns 当它运作良好时。
#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;
}
我使用 Valgrind 检查内存泄漏。上面的代码没有内存泄漏。当我删除 execvp
行时,它会检测到 definitely lost: 8 bytes in 2 blocks。为什么是这样?
Valgrind 命令:
valgrind --leak-check=full ./a.out
当我使用命令 valgrind --trace-children=yes --leak-check=full ./p4
==15091== Memcheck, a memory error detector
==15091== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==15091== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==15091== Command: ./p4
==15091==
==15092== Memcheck, a memory error detector
==15092== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==15092== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==15092== Command: /usr/bin/wc p4.c
==15092==
==15092==
==15092== HEAP SUMMARY:
==15092== in use at exit: 0 bytes in 0 blocks
==15092== total heap usage: 36 allocs, 36 frees, 8,809 bytes allocated
==15092==
==15092== All heap blocks were freed -- no leaks are possible
==15092==
==15092== For counts of detected and suppressed errors, rerun with: -v
==15092== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==15091==
==15091== HEAP SUMMARY:
==15091== in use at exit: 0 bytes in 0 blocks
==15091== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==15091==
==15091== All heap blocks were freed -- no leaks are possible
==15091==
==15091== For counts of detected and suppressed errors, rerun with: -v
==15091== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
[root cpu-api]#
无论我 malloc 多少字节,堆摘要总是说:
==15092== 堆总使用量:36 次分配,36 次释放,8,809 字节分配
什么是 "definitely lost" 内存?
首先,让我们讨论一下 Valgrind 报告为 "definitely lost" 的内容:如果在程序终止之前所有对已分配内存的引用都丢失,Valgrind 将报告已分配内存为 "definitely lost"。换句话说,如果您的程序达到这样一种状态,即由于不存在指向它的有效指针而无法释放分配的内存,这将算作 "definitely lost".
这意味着像这样的程序:
int main(void) {
char *buf = malloc(10);
// ...
exit(0);
}
将导致 Valgrind 无错误,而像这样的程序:
void func(void) {
char *buf = malloc(10);
// ...
} // memory is definitely lost here
int main(void) {
func();
exit(0);
}
会导致 "definitely lost" 错误。
为什么第一个版本适用于 Valgrind?这是因为系统在程序退出时 总是 释放内存。如果你一直使用分配的内存块直到程序结束,就真的没有必要显式调用 free()
了,它可以被认为只是浪费时间。出于这个原因,如果你不释放一些分配的块,同时仍然持有对它的引用,Valgrind 假设你这样做是为了避免 "useless" free()
因为你很聪明并且知道 OS 无论如何都会处理它。
但是,如果您忘记 free()
某些内容,并且丢失了对它的所有引用,那么 Valgrind 会警告您,因为您应该 free()
d 内存。如果你不这样做,并且程序保持 运行ning,那么每次进入错误块时都会发生同样的事情,你最终会浪费内存。这就是所谓的"memory leak"。一个非常简单的例子如下:
void func(void) {
char *buf = malloc(10);
// ...
} // memory is definitely lost here
int main(void) {
while (1) {
func();
}
exit(0);
}
此程序会使您的机器 运行 内存不足,并最终可能导致您的系统停止运行或冻结(警告:如果您不想冒冻结您的 PC 的风险,请不要测试此程序)。如果您在 func
结束之前正确调用 free(buf)
,则程序会无限期地保持 运行ning 而不会出现问题。
你的程序发生了什么
现在让我们看看您在何处分配内存以及在何处声明保存引用的变量。程序分配内存的唯一部分是在 if (rc == 0)
块内,通过 strdup
,此处:
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
对strdup()
的两次调用复制字符串并为此分配新内存。然后,您将对新分配内存的引用保存在 myargs
数组中,该数组在 if
块 中声明为 。如果您的程序在没有释放分配的内存的情况下退出该块,那么这些引用将会丢失,并且您的程序将无法释放内存。
与execvp()
:你的子进程被新进程(wc p4.c
)取代,父进程的内存space进程被操作系统丢弃(对于 Valgrind,这与程序终止完全相同)。此内存 而不是 被 Valgrind 计为丢失,因为在调用 execvp()
时,对已分配内存的引用仍然存在。注意:这是 而不是 因为您将指向已分配内存的指针传递给 execvp()
,这是因为原始程序有效终止并且内存由 OS 保留。
没有execvp()
:你的子进程继续执行,并且在它退出定义myargs
的代码块后,它 失去对已分配内存的任何引用 (因为 myargs[0]
和 myargs[1]
是唯一的引用)。然后 Valgrind 将其正确报告为 "definitely lost",2 个块(2 个分配)中的 8 个字节("wc"
为 3 个字节,"p4.c"
为 5 个字节)。如果对 execvp()
的调用因任何原因失败,也会发生同样的事情。
其他注意事项
公平地说,没有必要在您显示的程序中调用 strdup()
。这不像是因为它们在其他地方(或类似的地方)使用而需要复制这些字符串。代码可能只是:
myargs[0] = "wc"; // program: "wc" (word count)
myargs[1] = "p4.c"; // argument: file to count
无论如何,使用 exec*()
系列函数时的一个好习惯是直接在它后面放一个 exit()
,以确保程序不会保留 运行 ning 以防 exec*()
失败。像这样:
execvp(myargs[0], myargs);
perror("execvp failed");
exit(1);
exec()
系列函数用新的过程映像替换当前过程映像。这意味着调用进程当前正在 运行 的程序将被新程序替换,具有新初始化的堆栈、堆和(已初始化和未初始化的)数据段。
在子进程中,当你调用execvp()
execvp(myargs[0], myargs);
子进程替换为新进程(假设execvp()
成功)和子进程中分配的内存
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
将被新进程有效回收。因此,当您在子进程中有 execvp()
时,valgrind
不会报告任何内存泄漏。
When I delete the execvp line, it will detect definitely lost: 8 bytes in 2 blocks. Why is this?
来自 strdup():[强调]
char * strdup(const char *str1);
(dynamic memory TR)
Returns a pointer to a null-terminated byte string, which is a duplicate of the string pointed to by str1. The returned pointer must be passed to free to avoid a memory leak.
因此,当您的程序中没有 execvp()
调用时,子进程正在泄漏由 strdup()
分配的内存。要解决此问题,释放由 strdup()
分配的内存可能在 execvp()
之后,这样如果 execvp()
偶然失败或您明确地从程序中删除 execvp()
调用,它不应泄漏内存:
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
printf ("execvp failed\n"); // You may want to print the errno as well
free (myargs[0]);
free (myargs[1]);
请注意,无需分配内存并将字符串文字复制到该内存。您可以直接将字符串文字分配给 myargs
,像这样:
myargs[0] = "wc";
myargs[1] = "p4.c";
以下代码来自"Operating Systems: Three Easy Pieces"一书。代码让我很困惑。
我知道 execvp
永远不会 returns 当它运作良好时。
#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;
}
我使用 Valgrind 检查内存泄漏。上面的代码没有内存泄漏。当我删除 execvp
行时,它会检测到 definitely lost: 8 bytes in 2 blocks。为什么是这样?
Valgrind 命令:
valgrind --leak-check=full ./a.out
当我使用命令 valgrind --trace-children=yes --leak-check=full ./p4
==15091== Memcheck, a memory error detector
==15091== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==15091== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==15091== Command: ./p4
==15091==
==15092== Memcheck, a memory error detector
==15092== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==15092== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==15092== Command: /usr/bin/wc p4.c
==15092==
==15092==
==15092== HEAP SUMMARY:
==15092== in use at exit: 0 bytes in 0 blocks
==15092== total heap usage: 36 allocs, 36 frees, 8,809 bytes allocated
==15092==
==15092== All heap blocks were freed -- no leaks are possible
==15092==
==15092== For counts of detected and suppressed errors, rerun with: -v
==15092== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
==15091==
==15091== HEAP SUMMARY:
==15091== in use at exit: 0 bytes in 0 blocks
==15091== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==15091==
==15091== All heap blocks were freed -- no leaks are possible
==15091==
==15091== For counts of detected and suppressed errors, rerun with: -v
==15091== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
[root cpu-api]#
无论我 malloc 多少字节,堆摘要总是说: ==15092== 堆总使用量:36 次分配,36 次释放,8,809 字节分配
什么是 "definitely lost" 内存?
首先,让我们讨论一下 Valgrind 报告为 "definitely lost" 的内容:如果在程序终止之前所有对已分配内存的引用都丢失,Valgrind 将报告已分配内存为 "definitely lost"。换句话说,如果您的程序达到这样一种状态,即由于不存在指向它的有效指针而无法释放分配的内存,这将算作 "definitely lost".
这意味着像这样的程序:
int main(void) {
char *buf = malloc(10);
// ...
exit(0);
}
将导致 Valgrind 无错误,而像这样的程序:
void func(void) {
char *buf = malloc(10);
// ...
} // memory is definitely lost here
int main(void) {
func();
exit(0);
}
会导致 "definitely lost" 错误。
为什么第一个版本适用于 Valgrind?这是因为系统在程序退出时 总是 释放内存。如果你一直使用分配的内存块直到程序结束,就真的没有必要显式调用 free()
了,它可以被认为只是浪费时间。出于这个原因,如果你不释放一些分配的块,同时仍然持有对它的引用,Valgrind 假设你这样做是为了避免 "useless" free()
因为你很聪明并且知道 OS 无论如何都会处理它。
但是,如果您忘记 free()
某些内容,并且丢失了对它的所有引用,那么 Valgrind 会警告您,因为您应该 free()
d 内存。如果你不这样做,并且程序保持 运行ning,那么每次进入错误块时都会发生同样的事情,你最终会浪费内存。这就是所谓的"memory leak"。一个非常简单的例子如下:
void func(void) {
char *buf = malloc(10);
// ...
} // memory is definitely lost here
int main(void) {
while (1) {
func();
}
exit(0);
}
此程序会使您的机器 运行 内存不足,并最终可能导致您的系统停止运行或冻结(警告:如果您不想冒冻结您的 PC 的风险,请不要测试此程序)。如果您在 func
结束之前正确调用 free(buf)
,则程序会无限期地保持 运行ning 而不会出现问题。
你的程序发生了什么
现在让我们看看您在何处分配内存以及在何处声明保存引用的变量。程序分配内存的唯一部分是在 if (rc == 0)
块内,通过 strdup
,此处:
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
对strdup()
的两次调用复制字符串并为此分配新内存。然后,您将对新分配内存的引用保存在 myargs
数组中,该数组在 if
块 中声明为 。如果您的程序在没有释放分配的内存的情况下退出该块,那么这些引用将会丢失,并且您的程序将无法释放内存。
与execvp()
:你的子进程被新进程(wc p4.c
)取代,父进程的内存space进程被操作系统丢弃(对于 Valgrind,这与程序终止完全相同)。此内存 而不是 被 Valgrind 计为丢失,因为在调用 execvp()
时,对已分配内存的引用仍然存在。注意:这是 而不是 因为您将指向已分配内存的指针传递给 execvp()
,这是因为原始程序有效终止并且内存由 OS 保留。
没有execvp()
:你的子进程继续执行,并且在它退出定义myargs
的代码块后,它 失去对已分配内存的任何引用 (因为 myargs[0]
和 myargs[1]
是唯一的引用)。然后 Valgrind 将其正确报告为 "definitely lost",2 个块(2 个分配)中的 8 个字节("wc"
为 3 个字节,"p4.c"
为 5 个字节)。如果对 execvp()
的调用因任何原因失败,也会发生同样的事情。
其他注意事项
公平地说,没有必要在您显示的程序中调用 strdup()
。这不像是因为它们在其他地方(或类似的地方)使用而需要复制这些字符串。代码可能只是:
myargs[0] = "wc"; // program: "wc" (word count)
myargs[1] = "p4.c"; // argument: file to count
无论如何,使用 exec*()
系列函数时的一个好习惯是直接在它后面放一个 exit()
,以确保程序不会保留 运行 ning 以防 exec*()
失败。像这样:
execvp(myargs[0], myargs);
perror("execvp failed");
exit(1);
exec()
系列函数用新的过程映像替换当前过程映像。这意味着调用进程当前正在 运行 的程序将被新程序替换,具有新初始化的堆栈、堆和(已初始化和未初始化的)数据段。
在子进程中,当你调用execvp()
execvp(myargs[0], myargs);
子进程替换为新进程(假设execvp()
成功)和子进程中分配的内存
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
将被新进程有效回收。因此,当您在子进程中有 execvp()
时,valgrind
不会报告任何内存泄漏。
When I delete the execvp line, it will detect definitely lost: 8 bytes in 2 blocks. Why is this?
来自 strdup():[强调]
char * strdup(const char *str1); (dynamic memory TR)
Returns a pointer to a null-terminated byte string, which is a duplicate of the string pointed to by str1. The returned pointer must be passed to free to avoid a memory leak.
因此,当您的程序中没有 execvp()
调用时,子进程正在泄漏由 strdup()
分配的内存。要解决此问题,释放由 strdup()
分配的内存可能在 execvp()
之后,这样如果 execvp()
偶然失败或您明确地从程序中删除 execvp()
调用,它不应泄漏内存:
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
printf ("execvp failed\n"); // You may want to print the errno as well
free (myargs[0]);
free (myargs[1]);
请注意,无需分配内存并将字符串文字复制到该内存。您可以直接将字符串文字分配给 myargs
,像这样:
myargs[0] = "wc";
myargs[1] = "p4.c";