让我们先处理简单的事情:因为正在替换进程映像,所以永远不会调用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);
}
和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”调用一个程序,内存中会有一个包含@的区域987654338@ -- 然后将指向它们的指针放入同一区域的指针数组中,并将指向该指针的指针传递给 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 也是如此。如果您有兴趣,可以找到相当长的历史 OS here 列表;他们基本上都是以同样的方式做到的。事实上,我不知道有一个符合 POSIX 标准的系统以另一种方式来做这件事。这样的系统理论上可以存在;据我所知,它们实际上并没有。
简要介绍 Windows:它似乎做同样的事情;在几次尝试中,bar 中的argv[1] 直接位于argv[0] 后面,而argv[0] 直接位于argv 后面,在execl 之后的堆栈顶部。我找不到这方面的任何文档,但你可以说我有经验证据表明它也没有做任何聪明的事情。