关于 putenv() 和 setenv() 的问题

2024-11-07 08:55:00
admin
原创
35
摘要:问题描述:我对环境变量有一些思考,并且有一些疑问/观察。putenv(char *string);这个调用似乎存在致命缺陷。因为它不复制传递的字符串,所以您不能用本地调用它,并且不能保证堆分配的字符串不会被覆盖或意外删除。此外(虽然我还没有测试过),由于环境变量的一个用途是将值传递给子级环境,如果子级调用其中...

问题描述:

我对环境变量有一些思考,并且有一些疑问/观察。

  • putenv(char *string);

这个调用似乎存在致命缺陷。因为它不复制传递的字符串,所以您不能用本地调用它,并且不能保证堆分配的字符串不会被覆盖或意外删除。此外(虽然我还没有测试过),由于环境变量的一个用途是将值传递给子级环境,如果子级调用其中一个函数,这似乎毫无用处exec*()。我错了吗?

  • Linux 手册页指出 glibc 2.0-2.1.1 放弃了上述行为并开始复制字符串,但这会导致内存泄漏,该问题已在 glibc 2.1.2 中修复。我不清楚这个内存泄漏是什么或如何修复。

  • setenv()复制字符串,但我不知道它的具体工作原理。环境的空间是在进程加载时分配的,但它是固定的。这里是否有一些(任意的?)约定?例如,在 env 字符串指针数组中分配比当前使用的更多的插槽,并根据需要将空终止指针向下移动?新(复制的)字符串的内存是否分配在环境本身的地址空间中,如果它太大而无法容纳,您只会得到 ENOMEM?

  • 考虑到上述问题,是否有理由putenv()选择setenv()


解决方案 1:

  • [该] putenv(char *string);[...] 呼吁似乎存在致命缺陷。

是的,它存在致命缺陷。 它在 POSIX(1988 年)中被保留下来,因为那是现有技术。该setenv()机制后来才出现。 更正: POSIX 1990 标准在 §B.4.6.1 中说“考虑了附加函数putenv()clearenv(),但被拒绝了”。1997年的单一 Unix 规范putenv()(SUS) 第 2 版列出了但没有列出setenv()unsetenv()。下一个修订版(2004 年)确实定义了setenv()unsetenv()

因为它不会复制传递的字符串,所以您不能用本地调用它,并且不能保证堆分配的字符串不会被覆盖或意外删除。

您说得对,将局部变量传递给局部变量几乎总是一个糟糕的选择putenv()——异常几乎不存在。如果字符串是在堆上分配的(使用malloc()et al),则必须确保您的代码不会修改它。如果修改了,则同时修改了环境。

此外(虽然我还没有测试过),由于环境变量的一个用途是将值传递给子环境,因此如果子调用其中一个函数,这似乎毫无用处exec*()。我错了吗?

这些exec*()函数会复制环境并将其传递给执行的进程。这没有问题。

Linux 手册页指出 glibc 2.0-2.1.1 放弃了上述行为并开始复制字符串,但这会导致内存泄漏,该问题已在 glibc 2.1.2 中修复。我不清楚这个内存泄漏是什么或如何修复。

内存泄漏的原因是,一旦使用putenv()字符串调用,就无法再次将该字符串用于任何目的,因为您无法判断它是否仍在使用中,尽管您可以通过覆盖它来修改其值(如果将名称更改为在环境中的另一个位置找到的环境变量的名称,则结果不确定)。 因此,如果您已经分配了空间,putenv()则再次更改变量时会发生经典泄漏。 当putenv()开始复制数据时,分配的变量变为未引用,因为putenv()不再保留对参数的引用,但用户期望环境会引用它,因此内存泄漏。 我不确定修复方法是什么 - 我希望 3/4 它会恢复到以前的行为。

setenv()复制字符串,但我不知道它的具体工作原理。环境的空间是在进程加载时分配的,但它是固定的。

原始环境空间是固定的;当您开始修改它时,规则会发生变化。即使使用putenv(),原始环境也会被修改,并且可能会因添加新变量或将现有变量更改为具有更长的值而增长。

这里是否有某种(任意的?)惯例?例如,在 env 字符串指针数组中分配比当前使用的更多的插槽,并根据需要将空终止指针向下移动?

这就是该setenv()机制可能做的事情。(全局)变量environ指向指向环境变量的指针数组的开头。如果它一次指向一个内存块,而另一次指向另一个内存块,那么环境就会切换,就像这样。

新的(复制的)字符串的内存是否分配在环境本身的地址空间中,如果它太大而无法容纳,那么您只会得到 ENOMEM?

是的,您可以得到 ENOMEM,但您必须非常努力。如果您将环境扩大得太大,您可能无法正确执行其他程序 - 环境将被截断或执行操作将失败。

考虑到上述问题,有什么理由选择 putenv() 而不是 setenv() 呢?

  • 在新代码中使用setenv()

  • 更新旧代码以供使用setenv(),但不要将其作为首要任务。

  • 不要putenv()在新代码中使用。

解决方案 2:

没有特殊的“环境”空间 - setenv 只是malloc像平常一样动态地为字符串分配空间(例如)。由于环境不包含任何关于其中每个字符串来自何处的指示,因此不可能释放setenvunsetenv释放任何可能由先前对 setenv 的调用动态分配的空间。

“因为它不复制传递的字符串,所以您不能用本地函数调用它,而且不能保证堆分配的字符串不会被覆盖或意外删除。” putenv 的目的是确保如果您有一个堆分配的字符串,则可以故意删除它这就是理由文本中所说的“唯一可以添加到环境中而不允许内存泄漏的函数”。是的,您可以使用本地函数调用它,只需在从函数返回之前从环境中删除字符串(putenv("FOO=")或 unsetenv)。

重点是使用 putenv 使得从环境中删除字符串的过程完全具有确定性。而 setenv 在某些现有实现中,如果新值较短(以避免始终泄漏内存),则会修改环境中的现有字符串,并且由于它在调用 setenv 时进行了复制,因此您无法控制最初动态分配的字符串,因此在删除时无法释放它。

同时,setenv本身(或 unsetenv)无法释放前一个字符串,因为 - 即使忽略 putenv - 该字符串可能来自原始环境,而不是由之前调用 setenv 分配的。

(整个答案假设正确实现了 putenv,即不是提到的 glibc 2.0-2.1.1 中的那个。)

解决方案 3:

阅读The Open Group Base Specifications Issue 6 的手册页的RATIONALE部分。setenv

putenvsetenv都应该符合 POSIX 标准。如果您的代码putenv中包含 ,并且代码运行良好,请保留它。如果您正在开发新代码,您可能需要考虑setenv

如果您想查看( ) 或( )的实现示例,请查看glibc 源代码。setenv`stdlib/setenv.cputenvstdlib/putenv.c`

解决方案 4:

此外(虽然我还没有测试过),由于环境变量的一个用途是将值传递给子环境,因此如果子调用 exec() 函数之一,这似乎毫无用处。我错了吗?

这不是将环境传递给子进程的方式。所有各种类型exec()(您可以在手册的第 3 节中找到,因为它们是库函数)最终都会调用系统调用execve()(您可以在手册的第 2 节中找到)。参数如下:

   int execve(const char *filename, char *const argv[], char *const envp[]);

环境变量向量是显式传递的(可能部分由你的putenv()andsetenv()调用的结果构成)。内核将这些复制到新进程的地址空间中。从历史上看,环境的大小是有限制的,这个限制来自可用于此副本的空间(类似于参数限制),但我不熟悉现代 Linux 内核的限制。

解决方案 5:

我强烈建议不要使用这两个函数。只要你小心谨慎,并且只有一部分代码负责修改环境,这两个函数都可以安全使用,不会出现泄漏,但如果任何代码可能使用线程并可能读取环境(例如,出于时区、语言环境、dns 配置等目的),则很难正确使用,而且很危险。

我能想到的修改环境的唯一两个目的是在运行时更改时区,或者将修改后的环境传递给子进程。对于前者,您可能必须使用其中一个函数(setenv/ putenv),或者您可以environ手动更改它(如果您担心其他线程可能同时尝试读取环境,这可能exec更安全)。对于后一种用途(子进程),使用其中一个-family 函数,允许您指定自己的环境数组,或者简单地 clobber environ(全局)或在子进程中在setenv/之后但在 之前使用,在这种情况下,您不必关心内存泄漏或线程安全,因为没有其他线程,并且您即将破坏地址空间并将其替换为新的进程映像。putenv`fork`exec

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用