我一直在考虑环境变量,并有一些问题/观察。
putenv(char *string);
这个电话似乎有致命的缺陷。因为它不复制传递的字符串,所以无法使用本地调用它,并且无法保证堆分配的字符串不会被覆盖或意外删除。此外(尽管我还没有测试过),因为环境变量的一个用途是将值传递给子环境,如果孩子调用其中一个exec*()
函数,这似乎没用。我错了吗?
Linux手册页指出glibc 2.0-2.1.1放弃了上述行为并开始复制字符串,但这导致了glibc 2.1.2中修复的内存泄漏。我不清楚这个内存泄漏是什么或它是如何修复的。
setenv()
复制字符串,但我不确切知道它是如何工作的。当进程加载但是它被修复时,将分配环境空间。这里有一些(任意?)惯例吗?例如,在env字符串指针数组中分配比当前使用的更多的插槽,并根据需要向下移动空终止指针?是否在环境本身的地址空间中分配了新的(复制的)字符串的内存,如果它太大而不适合您只需获得ENOMEM?
考虑到上述问题,是否有理由更喜欢putenv()
而不是setenv()
?
答案 0 :(得分:37)
- [{]
putenv(char *string);
[...]电话似乎有致命缺陷。
是的,这是致命的缺陷。 它保存在POSIX(1988)中,因为那是现有技术。 更正: POSIX 1990标准在§B.4.6.1中说“附加功能 putenv()和< em> clearenv()被考虑但被拒绝“。 1997年的Single Unix Specification(SUS)版本2列出了setenv()
机制稍后到达。putenv()
,但未列出setenv()
或unsetenv()
。下一个修订版(2004年)确实同时定义了setenv()
和unsetenv()
。
因为它不复制传递的字符串,所以不能用本地调用它,并且不能保证堆分配的字符串不会被覆盖或意外删除。
你是正确的,一个局部变量几乎总是一个不好的选择传递给putenv()
- 例外是模糊到几乎不存在的点。如果在堆上分配字符串(使用malloc()
等),则必须确保代码不会对其进行修改。如果是,它会同时修改环境。
此外(尽管我还没有测试过),因为环境变量的一个用途是将值传递给子环境,如果孩子调用其中一个
exec*()
函数,这似乎没用。我错了吗?
exec*()
函数创建环境的副本并将其传递给已执行的进程。那里没问题。
Linux手册页指出glibc 2.0-2.1.1放弃了上述行为并开始复制字符串,但这导致了glibc 2.1.2中修复的内存泄漏。我不清楚这个内存泄漏是什么或它是如何修复的。
内存泄漏的产生是因为一旦你用一个字符串调用putenv()
,就不能再将该字符串用于任何目的,因为你无法判断它是否仍在使用中,尽管你可以修改它的值覆盖它(如果将名称更改为在环境中另一个位置找到的环境变量的名称,则会产生不确定的结果)。因此,如果您已分配空间,则如果再次更改变量,则经典putenv()
会泄漏它。当putenv()
开始复制数据时,分配的变量变为未引用,因为putenv()
不再保留对参数的引用,但是用户期望环境引用它,因此内存被泄露。我不确定修复是什么 - 我希望它能够恢复原来的行为。
setenv()
复制字符串,但我不确切知道它是如何工作的。在进程加载时会分配环境空间,但它是固定的。
原始环境空间是固定的;当你开始修改它时,规则就会改变。即使使用putenv()
,原始环境也会被修改,并且可能会因添加新变量而增加,或者由于更改现有变量而具有更长的值。
这里有一些(任意?)惯例吗?例如,在env字符串指针数组中分配比当前使用的更多的插槽,并根据需要向下移动空终止指针?
这就是setenv()
机制可能做的事情。 (全局)变量environ
指向环境变量指针数组的开头。如果它一次指向一个内存块而在不同时间指向一个不同的块,那么环境就会被切换,就像那样。
新(复制)字符串的内存是否在环境本身的地址空间中分配,如果它太大而不适合您,只需获得ENOMEM?
嗯,是的,你可以得到ENOMEM,但你必须非常努力。如果你的环境变得太大,你可能无法正确执行其他程序 - 环境将被截断,或者exec操作将失败。
考虑到上述问题,有没有理由更喜欢putenv()而不是setenv()?
setenv()
。setenv()
,但不要将其作为最高优先级。putenv()
。答案 1 :(得分:5)
阅读The Open Group Base Specifications Issue 6中setenv
手册页的RATIONALE部分。
putenv
和setenv
都应该符合POSIX标准。如果您的代码中包含putenv
,并且代码运行良好,请不要管它。如果您正在开发新代码,可能需要考虑setenv
。
如果您想查看setenv
(glibc source code)或putenv
(stdlib/setenv.c
)的实施示例,请查看stdlib/putenv.c
。
答案 2 :(得分:5)
没有特殊的“环境”空间 - setenv只是动态地为字符串分配空间(例如malloc
),就像你通常那样。由于环境不包含其中每个字符串来自何处的任何指示,因此setenv
或unsetenv
不可能释放任何可能由先前调用setenv动态分配的空间。< / p>
“因为它不复制传递的字符串,所以不能用本地调用它,并且不能保证堆分配的字符串不会被覆盖或意外删除。” putenv的目的是确保如果你有一个堆分配的字符串,就可以将其删除。这就是理由文本的意思是“唯一可用于在不允许内存泄漏的情况下添加到环境中的函数”。是的,您可以使用本地调用它,只需从函数返回之前从环境中删除字符串(putenv("FOO=")
或unsetenv)。
关键是使用putenv使得从环境中删除字符串的过程完全具有确定性。 setenv将在某些现有实现上修改环境中的现有字符串,如果新值更短(以避免始终泄漏内存),并且因为它在您调用setenv时创建了一个副本而您不在控制最初动态分配的字符串,以便在删除它时不能释放它。
与此同时,setenv 本身(或unsetenv)无法释放前一个字符串,因为 - 即使忽略putenv - 字符串可能来自原始环境,而不是之前调用的SETENV。
(这个完整的答案假设一个正确实现的putenv,即不你提到的glibc 2.0-2.1.1中的那个。)
答案 3 :(得分:4)
此外(尽管我还没有测试过),因为环境变量的一个用途是将值传递给子环境,如果子进程调用exec()函数之一,这似乎没用。我错了吗?
这不是环境传递给孩子的方式。 exec()
的所有各种风格(您在手册的第3部分中找到它们都是库函数)最终会调用系统调用execve()
(您可以在本手册的第2部分找到)。论点是:
int execve(const char *filename, char *const argv[], char *const envp[]);
环境变量的向量是明确传递的(可能部分是根据putenv()
和setenv()
调用的结果构建的)。内核将这些复制到新进程的地址空间中。从历史上看,从可用于此副本的空间派生的环境大小有限(类似于参数限制),但我不熟悉对现代Linux内核的限制。
答案 4 :(得分:3)
我强烈建议不要使用这些功能中的任何一个。只要您小心并且只有一部分代码负责修改环境,可以安全且无泄漏地使用,但如果任何代码可能存在,则很难正确且危险使用线程并可能读取环境(例如,用于时区,区域设置,dns配置等目的)。
我可以考虑修改环境的唯一两个目的是在运行时更改时区,或者将修改后的环境传递给子进程。对于前者,您可能必须使用其中一个函数(setenv
/ putenv
),或者您可以手动走environ
来更改它(此可能如果你担心其他线程可能会同时尝试读取环境,那么会更安全。对于后者使用(子进程),使用exec
- 系列函数之一,可以指定自己的环境数组,或者只使用clobber environ
(全局)或使用setenv
/在putenv
之后fork
之前的子进程中exec
,在这种情况下,您不必关心内存泄漏或线程安全,因为没有其他线程而且您'重新破坏你的地址空间并用新的过程映像替换它。