以下代码来自《操作系统:三个简单部分》一书。代码让我困惑。我知道execvp
在运行良好的情况下永远不会返回。
#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 failedn");
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
行时,它肯定会检测到丢失:2个块中有8个字节。为什么会这样?
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个释放,8809个字节分配
什么是"肯定丢失的"内存
首先,让我们讨论一下Valgrind报告的"肯定丢失"的内容:如果在程序终止之前,所有对分配内存的引用都丢失了,那么Valgrind将报告分配内存为"绝对丢失"。换句话说,如果您的程序达到这样一种状态,即由于不存在指向它的有效指针而无法释放已分配的内存,则这将被视为"肯定丢失"。
这意味着像这样的程序:
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);
}
将导致"肯定丢失"的错误。
为什么第一个版本适用于Valgrind?这是因为内存总是在程序退出时由系统释放。如果您一直使用分配的内存块,直到程序结束,那么实际上不需要显式调用它的free()
,这可以被认为只是浪费时间。因此,如果你在保留对某个已分配块的引用的同时没有释放该块,Valgrind会认为你这样做是为了避免"无用"的free()
,因为你很聪明,知道操作系统无论如何都会处理它。
然而,如果你忘记了free()
,并且失去了对它的所有引用,那么Valgrind会警告你,因为你应该有free()
的记忆。如果你不这样做,并且程序一直在运行,那么每次进入bug块时都会发生同样的事情,你最终会浪费内存。这就是所谓的"内存泄漏"。一个非常简单的例子如下:
void func(void) {
char *buf = malloc(10);
// ...
} // memory is definitely lost here
int main(void) {
while (1) {
func();
}
exit(0);
}
这个程序会让你的机器内存不足,最终可能会杀死或冻结你的系统(警告:如果你不想冒冻结电脑的风险,请不要测试这个)。如果在func
结束之前正确地调用free(buf)
,那么程序将无限期地运行而不会出现问题。
你的程序中发生了什么
现在,让我们看看在哪里分配内存,以及在哪里声明包含引用的变量。程序中唯一分配内存的部分位于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
)替换,父进程的内存空间被操作系统丢弃(对于Valgrind,这与程序终止完全相同)。Valgrind不会将该内存计入丢失,因为调用execvp()
时,对分配内存的引用仍然存在。注意:这是而不是,因为您将指向已分配内存的指针传递给execvp()
,这是因为原始程序实际上终止了,内存由操作系统保留。
如果没有execvp()
:您的子进程继续执行,并且在它退出定义myargs
的代码块之后,它将丢失对分配内存的任何引用(因为myargs[0]
和myargs[1]
是唯一的引用)。Valgrind随后正确地将其报告为"肯定丢失",在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()
,以确保程序在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);(动态存储器TR)
返回指向以null结尾的字节字符串的指针,该字符串与str1所指向的字符串重复必须将返回的指针传递给free,以避免内存泄漏
因此,当程序中没有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 failedn"); // You may want to print the errno as well
free (myargs[0]);
free (myargs[1]);
请注意,不需要分配内存并将字符串文字复制到该内存中。您可以直接将字符串文字分配给myargs
,如下所示:
myargs[0] = "wc";
myargs[1] = "p4.c";