让我们先处理简单的事情:因为进程图像正在被替换,所以
std::string
的析构函数永远不会被调用,因此内存不会消失(那样)。
我假设你是在询问类UNIX操作系统,因为Windows上不存在
unistd.h
,所以相关标准是
POSIX。它在这个领域故意模糊,并且只规定:
argv[]和envp[]指针数组及其指向的字符串,在调用exec函数时除了替换进程图像的结果之外,不得被修改。
这意味着
exec
将确保参数不会被替换进程图像而失效,但是POSIX并不关心
exec
如何实现这一点。这是您可以依赖的部分:您的参数将保持有效,不会变得损坏。
关于"实际操作中:" POSIX确实有一个想法,即当标准被编写时实现如何执行,而最近的实现并没有真正改变基本机制。 让我们在字里行间读一下:
可用于新进程的组合参数和环境列表的字节数为{ARG_MAX}。
ARG_MAX
被定义here为最小值4096。
如果我们假设为参数和环境分配了固定大小的空间(或者至少可以增长到固定的最大大小),则此要求是有意义的,并且只有在进程映像被替换之前将参数复制到该空间才有意义。 POSIX不会强求这样做,但是有隐含的假设存在,事实上这是许多(也许所有)系统使用的方式。 此外,它们通常(或许总是)以相同的方式执行。
让我们看看Linux。取以下两个程序foo
:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main() {
char *p = strdup("foobar");
printf("%p\n", p);
execl("bar", "bar", p, NULL);
}
and bar
:
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("%p\n", argv[1]);
return 0;
}
调用foo
会在x86-64 Linux上输出:
0x7f6010
0x7fffbefd6ae5
意思是我传递的字符串在
exec
期间改变了位置。地址
0x7fffbefd6ae5
该程序位于主线程的调用栈顶部(由ASLR向下移动了一些,以 0x7fffffffffff
为基准)。在Linux上发生的情况(您可以使用gdb查看)是将参数直接复制到此区域中,如果使用“bar baz qux xyzzy”调用程序,则会有一个包含 "bar\0baz\0qux\0xyzzy"
的内存区域,然后将它们的指针放入同一区域的指针数组中,并将指向该数组的指针传递给main函数。(环境也会被复制到该区域,但这不是问题的一部分。)
在Linux上,该区域沿内存页边界分配;直到Linux 2.6.31,它最多可以增长到32个页面(128 KB)。自2.6.32以来,限制为堆栈大小的四分之一(由ulimit确定)。
让我们来看看FreeBSD:使用相同的程序,在i386 FreeBSD 9.1上输出如下:
0x28404050
0xbfbfee58
了解到FreeBSD的堆栈从0xbfc00000
开始(在9.1中尚未启用ASLR),我们可以看到这里发生了同样的事情。FreeBSD使用固定的最大大小为256KB,MacOS X也是如此。如果您有兴趣,可以在这里找到一个相当长的历史操作系统列表;它们基本上都以相同的方式完成。实际上,我不知道有哪个符合POSIX标准的系统会以其他方式执行。这样的系统理论上可能存在;据我所知,实际上不存在。
关于Windows的简要介绍:它似乎也在做同样的事情;经过几次尝试,在bar
的argv[1]
直接位于execl
之后堆栈顶部的argv
之后,argv[0]
直接位于其后面。我无法找到任何关于此的文档,但您可以说我有经验证据表明它也没有做任何聪明的事情。