下面的代码来自《操作系统:三个简单的部分》一书。代码让我很困惑。我知道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 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;
}
Run Code Online (Sandbox Code Playgroud)
我使用 Valgrind 来检查内存泄漏。上面的代码没有内存泄漏。当我删除该execvp行时,它会检测到肯定丢失:2 个块中的 8 个字节。为什么是这样?
Valgrind 命令:
valgrind --leak-check=full ./a.out
Run Code Online (Sandbox Code Playgroud)
当我使用命令 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]#
Run Code Online (Sandbox Code Playgroud)
无论我分配多少字节,堆摘要总是说:==15092== 堆总使用量:36 个分配,36 个释放,8,809 个已分配字节
首先,让我们讨论一下 Valgrind 报告“肯定丢失”的内容:如果在程序终止之前对已分配内存的所有引用都丢失了,Valgrind 将报告分配的内存为“肯定丢失”。换句话说,如果您的程序达到一种状态,其中由于不存在指向它的有效指针而无法释放已分配的内存,则这将计为“绝对丢失”。
这意味着这样的程序:
int main(void) {
char *buf = malloc(10);
// ...
exit(0);
}
Run Code Online (Sandbox Code Playgroud)
不会导致Valgrind出错,而这样的程序:
void func(void) {
char *buf = malloc(10);
// ...
} // memory is definitely lost here
int main(void) {
func();
exit(0);
}
Run Code Online (Sandbox Code Playgroud)
将导致“绝对丢失”错误。
为什么第一个版本适用于 Valgrind?这是因为系统总是在程序退出时释放内存。如果你一直使用分配的内存块直到你的程序结束,真的没有必要显式调用free()它,这可以被认为只是浪费时间。出于这个原因,如果您在仍然持有对它的引用的同时不释放一些已分配的块,Valgrind 会假设您这样做是为了避免“无用”,free()因为您很聪明并且知道操作系统无论如何都会处理它.
但是,如果您忘记了free()某事,并且丢失了对它的所有引用,那么 Valgrind 会警告您,因为您应该拥有free()记忆。如果你不这样做,并且程序继续运行,那么每次进入有问题的块时都会发生同样的事情,你最终会浪费内存。这就是所谓的“内存泄漏”。一个非常简单的例子如下:
void func(void) {
char *buf = malloc(10);
// ...
} // memory is definitely lost here
int main(void) {
while (1) {
func();
}
exit(0);
}
Run Code Online (Sandbox Code Playgroud)
该程序将使您的机器内存不足,最终可能导致系统死亡或冻结(警告:如果您不想冒冻结 PC 的风险,请不要进行测试)。如果您free(buf)在 结束之前正确调用func,则程序将无限期地运行而不会出现问题。
现在让我们看看您在哪里分配内存以及在哪里声明保存引用的变量。分配内存的程序的唯一部分是在if (rc == 0)块内,通过strdup,这里:
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
Run Code Online (Sandbox Code Playgroud)
这两个调用strdup()复制字符串并为此分配新内存。然后,在myargs数组中保存对新分配内存的引用,该引用在if块内声明。如果您的程序在没有释放分配的内存的情况下退出块,那么这些引用将丢失,并且您的程序将无法释放内存。
用execvp():你的子进程被新进程(wc p4.c)代替,父进程的内存空间被操作系统扔掉(对于Valgrind,这和程序终止完全一样)。Valgrind不会将该内存计为丢失,因为在execvp()调用时仍然存在对已分配内存的引用。注意:这不是因为您将指向已分配内存的指针传递给execvp(),而是因为原始程序有效地终止并且操作系统保留了内存。
没有execvp():您的子进程继续执行,并且在它退出myargs定义的代码块之后,它会丢失对已分配内存的任何引用(因为myargs[0]并且myargs[1]是唯一的引用)。Valgrind 然后正确地将其报告为“绝对丢失”,2 个块(2 个分配)中的8 个字节(3 个 for"wc"和 5 个 for "p4.c")。如果由于execvp()任何原因调用失败,也会发生同样的事情。
公平地说,没有真正需要调用strdup()您展示的程序。不像那些字符串需要复制,因为它们在其他地方使用(或类似的东西)。代码可能只是:
myargs[0] = "wc"; // program: "wc" (word count)
myargs[1] = "p4.c"; // argument: file to count
Run Code Online (Sandbox Code Playgroud)
在任何情况下,使用exec*()函数族时的一个好习惯是exit()直接在它之后放置一个,以确保程序在exec*()失败时不会继续运行。像这样的东西:
execvp(myargs[0], myargs);
perror("execvp failed");
exit(1);
Run Code Online (Sandbox Code Playgroud)