【问题标题】:Is there a function for iterating through POSIX environment variables?是否有遍历 POSIX 环境变量的函数?
【发布时间】:2015-03-23 16:54:47
【问题描述】:

我刚刚阅读了有关 complexities of environ 的内容,特别是它是多么的线程不安全,因为 assign to it 可以合法地替换整个环境。

请记住,是否有任何类型的 API 用于迭代所有当前不直接使用静态 environ 数组的环境变量?

需要明确的是,我知道 getenv()setenv() 和朋友们(关于 putenv() 的说法越少,IMO 越好)——这些只允许恢复特定变量,而不是遍历所有变量。

我还编写了大量直接遍历environ 的代码。但令我震惊的是,在任何明智的多线程应用程序中,使用该环境的唯一明智的方法是尽早在main() 中插入代码以将其插入unordered_map 或类似的,并且只从那里使用它;或者只是将它视为完全不可变的,并希望你们链接的库都不会这样做。

所以我想知道是否有任何更安全的接口被定义为 POSIX 的一部分,或者可能是特定于平台的,而我不知道这些接口?

【问题讨论】:

  • 除了购物清单问题之外,是否有一个库可以做 x... 如果你想要地图之类的东西,你可以专注于 cpp,因为在标准库...
  • 确实,在纯 C 语言中,您需要使用已分配的 char 缓冲区数组来代替 - 但复制的原理与我的意思相同。就购物清单而言,也许 - 但 POSIX API 通常比环境处理的糟糕混乱要好。特别是在需要的地方通常有可重入的函数变体,但我认为能够修改 environ 的所需行为会阻碍任何将访问包装在一个好的 API 中并仍然保持兼容的努力。

标签: c++ c environment-variables posix


【解决方案1】:

在多线程应用程序中,使用环境的唯一明智方法是将其视为只读的。如果你需要修改它,你应该在程序初始化阶段,在启动任何线程之前进行。

由于环境的目的是从环境到应用程序进行通信,因此将环境视为只读应该不是问题。与此用例一致,除了使用 environ 之外,没有用于迭代环境的标准接口,并且不能在多线程应用程序中安全使用,除非应用程序承诺不修改环境。

据我所知,没有标准库函数会修改环境(setenvputenv 除外),而且 IMO 也没有健全的库会这样做。

我们认识到偶尔需要修改环境,或者引入默认值,或者作为分叉子节点初始化序列的一部分。在前一种情况下,通常可以在启动线程之前执行上述修改。

后一种情况在多线程应用程序中很棘手,但无论如何,只在子进程中修改环境很容易(即在调用fork() 之后和调用exec*() 之前。或者,一个可以构建全新的环境并将其提供给接受环境参数的exec() 版本。

简而言之,使用环境作为全局变量的代理甚至比首先使用全局变量更糟糕(恕我直言)。但是,将它用于它的发明目的 - 由其父进程配置子进程 - 不会导致问题。


在 cmets 进行了一次有趣的讨论后,似乎值得添加一些注释。

首先,许多标准(和非标准)库函数读取环境。这对于调试来说特别方便,但它也用于许多配置选项,包括执行路径搜索,语言环境,控制台窗口大小,时区等等等等。(在Base中有一个很长但不完整的列表Posix 标准的定义卷。)

由于getenv不能在多线程代码中安全使用,除非知道不能对环境进行并发修改,因此禁止标准库函数修改环境似乎是合理的(除了为此类修改设计的接口) )。据我所知,Posix 不包括此禁令,但它确实要求所有接口都记录它们对环境的使用,我不相信除了putenvsetenv 和@987654334 之外我还没有看到任何接口@ 记录在案以修改环境。总体而言,我认为在多线程代码启动线程后,假设环境不会被修改是完全合理的(甚至是必要的)。

当然,在启动线程之前在单线程代码或多线程代码中修改环境是合法的。但最佳实践要求只能使用两种可能的修改机制之一:

  1. setenv(和unsetenv)接口。
  2. environ 直接分配给不同的数组,其中原始environ 中存在的所有字符串都已复制到应用程序管理的内存中。

putenv 的使用确实不可取,但如果不与上述其他两种可能性混合使用是可以接受的。

同样,Posix 不提供限制、指导或建议(除了更喜欢 setenv 而不是 putenv),所以将以上内容作为我的建议。

【讨论】:

  • 谢谢。这或多或少符合我的理解,但是环境 API 非常糟糕(即使对于 POSIX!)我想我一定是遗漏了一些东西。您可以分配给environ 的方式 - 但后来调用setenv() 检测到这个并将其复制回来......这是内存管理的噩梦!对于您指定的后一个问题,我通常使用posix_spawn(),并显式构建环境。我发现它比我需要运行外部二进制文件的 fork/exec 更方便。
  • @cartroo:一般来说,setenv 不会这样做,但至少有一个实现会这样做/这样做。但是如果实现想要的话,这个条款就在那里,它可能想要解决(部分)内存管理问题的原因。我同意putenv 是可怕的;正如基本原理所解释的那样(如果您在字里行间阅读),它被包括在内是因为一些历史上的 unices 有它,而另一些则有 setenv,唯一可能的调解是包括两者。但是,setenv 是明确首选的。我坚持我写的:不要将环境用作全局变量的代理......
  • 是的,我当然不是要暗示除非必要,否则将环境用于任何事情是一个好主意,但可悲的事实是,一些旧库可能依赖于它们,尤其是在调试方面。有时以编程方式启用它很有用。回复setenv() 我认为在应用程序重新分配整个environ 的情况下肯定必须复制?这是替换整个环境的明确支持的方式。否则它不会知道用户分配的数组的大小。
  • 基于this page,分配给environ似乎是合法的,这意味着setenv()在添加新变量时必须实现复制行为。 This page 表示如果 environ 已分配给 setenv() 的行为未定义,但这是问题 6 - 看起来 issue 7 使它更加模棱两可。令人困惑!
  • @Cartroo:当然,很多库都依赖于读取环境,即使在多线程代码中也完全安全,只要不尝试修改环境。因此,任何修改(例如配置调试选项)都应该在启动线程之前进行。 Posix 没有这样说,但它不是标准提供使用建议的要求。
【解决方案2】:

“所以我想知道是否有任何更安全的接口被定义为 POSIX 的一部分,或者可能是特定于平台的,而我不知道这些接口?”

我不认识任何人。

“但让我感到震惊的是,在任何明智的多线程应用程序中,使用该环境的唯一明智的方法是尽早在 main() 中插入代码以将其放入 unordered_map 或类似的并且只能从那里使用它;或者只是将其视为完全不可变的,并希望你们链接的库都不会这样做。”

这不会使任何事情更安全,因为如果从其他任何地方(可能来自不受您控制的代码)调用了setenv()putenv(),则需要更新该地图。

【讨论】:

  • 感谢您的回答。确实,如果您不能依赖链接中的所有代码将环境视为只读,那么我认为您是在自找麻烦……但是,正如 Rici 上面所说,修改实际环境的库(而不是将自己的副本传递给posix_spawn() 或类似的)应该很少!
猜你喜欢
  • 2021-08-11
  • 1970-01-01
  • 2020-07-24
  • 2017-06-13
  • 1970-01-01
  • 1970-01-01
  • 2011-03-19
  • 1970-01-01
  • 2023-01-27
相关资源
最近更新 更多