我知道环境本质上是一个指向字符串的指针数组。因此,释放这些字符串占用的内存将导致这些环境变量丢失。
如果我为环境字符串分配内存,然后用于putenv()
设置已存在的变量,是否会导致内存泄漏?
在以下C程序中演示:
char ** environ;
main()
{
/*
Code for printing all environment variables
*/
char * temp = (char *)malloc( 64 * sizeof(char));
strcat(temp, "");
strcat(temp, "PWD=/home/mycomputer/");
putenv(temp);
/*
Code for printing all environment variables
*/
}
这里PWD
环境变量已经指向一些内存(其中包含路径),现在我已经分配了更多内存(其中包含路径)。因此,指针移动到新位置,旧内存无法访问,但仍已分配。那么我说程序出现了内存泄漏是正确的吗?如果不是,那么这个变化是如何被容纳在内存中的呢?
答案1
你的观察是正确的。您分配的新字符串malloc
将替换旧字符串,并且不再使用旧字符串。如果旧字符串是程序启动时传递的环境的一部分(即,这是该过程中第一次更改变量),则旧字符串是堆栈段的一部分。
这是否是内存泄漏还有待商榷。您无法释放旧字符串占用的内存,但也没有明智的方法来重用该内存。如果你putenv
多次调用,free
当字符串不再使用时,你当然可以使用你自己分配的内存。
请注意, 的语义putenv
取决于 glibc 的版本,如文档中所述手册页。
顺便说一句,你的程序中有一个错误,而不是
strcat(temp, "");
strcat(temp, "PWD=/home/mycomputer/");
你应该写
strcpy(temp, "PWD=/home/mycomputer/");
答案2
环境由三部分组成。
一个双指针,比如 envp,称为
environment list pointer
。它指向
environment list
,您可以将其想象为一个指针数组,它指向环境字符串。, 的形式
environment strings
为name=value
。
现在,它存储在进程的虚拟机布局中的哪里?
答案是——栈之上;我们可以称之为较高地址。
这有一个混凝土天花板和混凝土底部;从上面开始,你就无法扩展,因为它是布局的天花板;由于堆栈的存在,下面您无法扩展。
现在,回答你的问题——
当您将字符串更改为大于较高段可以容纳的长度的任何值时,它在内部本质上会执行malloc
.
然后,环境列表中的指针更改为指向该分配的内存。 因此,您将有一个环境列表,其中一些指针仍然指向较高内存中的地址,而该特定指针将指向堆上的某个区域。在这个特殊情况下修改参数的整个环境列表不会复制到堆中。
指针指向的前一个内存会发生什么情况?嗯,它看起来类似于内存泄漏,但通常不是泄露,由于该内存已经存在并且现在是一个无用的块,因此无法再指向它。
我不会称其为内存泄漏,因为它不是动态分配,而是已经存在的静态分配;保留段的一部分。
内存泄漏听起来像是内存的某些部分,您可以将其用于其他用途,但不能,因为无法访问它。但无论如何,这块位于较高地址的内存一开始就无法用于其他用途。
所以,看起来像是内存泄漏;但我通常不会这样称呼它!
对于造成的混乱,我深表歉意,我已尽力表达清楚:)
答案3
很遗憾您从第一个版本中修改了您的问题,setenv()
因为setenv()
做内存泄漏,至少在 glibc 实现中是这样。
以这个示例程序为例;它会不断增长,直到耗尽所有内存:
#include <stdlib.h>
#include <stdio.h>
int main(void){
char buf[24];
for(;;){
snprintf(buf, sizeof buf, "%d", rand());
setenv("foo", buf, 1);
}
}
在 glibc 实现中,setenv()
永远不会释放先前已分配的内存setenv()
;然而,它会通过在二叉树中跟踪由它分配的环境字符串来避免重复(使用tfind(3)
, 与列表分开char **environ
)。这也具有隐藏泄漏等工具的良好效果valgrind
;-)
至于putenv()
,它依赖于调用者来管理作为参数传递给它的字符串。因此,调用者需要确保它在重复调用时不会泄漏它putenv()
,当它仍然是环境的一部分时它不会释放它,并且它没有使用自动/堆栈变量。
此外,在调用后修改字符串 itputenv()
可能会删除环境变量或添加另一个环境变量。例子:
#define _XOPEN_SOURCE 500
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <unistd.h>
int main(void){
extern char **environ;
static char *my_environ[] = { "YUCK=yumm", 0 };
static char str[256] = "FOO=bar";
environ = my_environ;
putenv(str);
snprintf(str, sizeof str, "BAZ=quux");
execl("/usr/bin/printenv", "printenv", (void*)0);
err(1, "execlp");
}
这是标准所要求的,但在旧版本的 glibc 中不起作用,旧版本putenv()
会复制其参数。
最后,当向环境添加新变量时,setenv()
和putenv()
都会重新定位数组。char **environ
该实现将保留自己的指针副本并使用 增长它realloc()
,但不会弄乱其原始指针(指向静态内存),或使用例如手动分配的内存。environ = calloc(sizeof *environ, envlen)
。