当我们对已经存在的环境变量使用 putenv() 时,内存会发生什么?

当我们对已经存在的环境变量使用 putenv() 时,内存会发生什么?

我知道环境本质上是一个指向字符串的指针数组。因此,释放这些字符串占用的内存将导致这些环境变量丢失。

如果我为环境字符串分配内存,然后用于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

环境由三部分组成。

  1. 一个双指针,比如 envp,称为environment list pointer

  2. 它指向environment list,您可以将其想象为一个指针数组,它指向环境字符串。

  3. , 的形式environment stringsname=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)

相关内容