簡體   English   中英

關於 putenv() 和 setenv() 的問題

[英]Questions about putenv() and setenv()

我一直在思考環境變量,並有一些問題/意見。

  • putenv(char *string);

    這個電話似乎有致命的缺陷。 因為它不復制傳遞的字符串,所以你不能用本地調用它,並且不能保證堆分配的字符串不會被覆蓋或意外刪除。 此外(雖然我沒有測試過),因為環境變量的一種用途是將值傳遞給孩子的環境,如果孩子調用exec*()函數之一,這似乎沒有用。 我錯了嗎?

  • Linux 手冊頁表明 glibc 2.0-2.1.1 放棄了上述行為並開始復制字符串,但這導致了 Glibc 2.1.2 中修復的 memory 泄漏。 我不清楚這個 memory 泄漏是什么或如何修復的。

  • setenv()復制字符串,但我不知道它是如何工作的。 進程加載時為環境分配空間,但它是固定的。 這里有一些(任意的?)約定嗎? 例如,在 env 字符串指針數組中分配比當前使用更多的插槽,並根據需要向下移動 null 終止指針? memory 是否用於在環境本身的地址空間中分配的新(復制的)字符串,如果它太大而不適合您只需獲取 ENOMEM?

  • 考慮到上述問題,是否有任何理由更喜歡putenv()而不是setenv()

  • [The] putenv(char *string); [...] 通話似乎有致命的缺陷。

是的,這是致命的缺陷。 它被保存在 POSIX (1988) 中,因為那是現有技術。 setenv()機制稍后出現。 更正: POSIX 1990 標准在 §B.4.6.1 中說“附加函數 putenv ()clearenv()被考慮但被拒絕”。 1997 年的單一 Unix 規范(SUS) 版本 2 列出了putenv()但未列出setenv()unsetenv() 下一個版本(2004 年)也確實定義了setenv()unsetenv()

因為它不復制傳遞的字符串,所以你不能用本地調用它,並且不能保證堆分配的字符串不會被覆蓋或意外刪除。

您是正確的,局部變量幾乎總是傳遞給putenv()的錯誤選擇——異常模糊到幾乎不存在的地步。 如果字符串是在堆上分配的(使用malloc()等),您必須確保您的代碼不會修改它。 如果是這樣,它同時也在修改環境。

此外(雖然我沒有測試過),因為環境變量的一種用途是將值傳遞給孩子的環境,如果孩子調用exec*()函數之一,這似乎沒有用。 我錯了嗎?

exec*()函數制作環境的副本並將其傳遞給執行的進程。 那里沒有問題。

Linux 手冊頁表明 glibc 2.0-2.1.1 放棄了上述行為並開始復制字符串,但這導致了 Glibc 2.1.2 中修復的 memory 泄漏。 我不清楚這個 memory 泄漏是什么或如何修復的。

memory 泄漏的出現是因為一旦你用一個字符串調用了 putenv putenv() ,你就不能出於任何目的再次使用該字符串,因為你無法判斷它是否仍在使用中,盡管你可以通過覆蓋它來修改值(不確定如果您將名稱更改為在環境中另一個 position 中找到的環境變量的名稱)。 因此,如果您已經分配了空間,那么如果您再次更改變量,經典的putenv()就會泄漏它。 putenv()開始復制數據時,分配的變量變為未引用,因為putenv()不再保留對參數的引用,但用戶期望環境會引用它,因此 memory 被泄露。 我不確定修復是什么——我 3/4 預計它會恢復到舊的行為。

setenv()復制字符串,但我不知道它是如何工作的。 進程加載時為環境分配空間,但它是固定的。

原有的環境空間是固定的; 當你開始修改它時,規則就會改變。 即使使用putenv() ,原始環境也會被修改,並且可能由於添加新變量或將現有變量更改為具有更長值的結果而增長。

這里有一些(任意的?)約定嗎? 例如,在 env 字符串指針數組中分配比當前使用更多的插槽,並根據需要向下移動 null 終止指針?

這就是setenv()機制可能會做的事情。 (全局)變量environ指向環境變量指針數組的開頭。 如果它一次指向 memory 的一個塊,而在不同的時間指向另一個塊,則環境切換,就像這樣。

memory 是否用於在環境本身的地址空間中分配的新(復制的)字符串,如果它太大而不適合您只需獲取 ENOMEM?

嗯,是的,你可以得到 ENOMEM,但你必須非常努力。 而且,如果您將環境增長得太大,您可能無法正確執行其他程序 - 環境將被截斷或 exec 操作將失敗。

考慮到上述問題,是否有任何理由更喜歡 putenv() 而不是 setenv()?

  • 在新代碼中使用setenv()
  • 更新舊代碼以使用setenv() ,但不要將其作為首要任務。
  • 不要在新代碼中使用putenv()

閱讀 The Open Group Base Specifications Issue 6 中setenv手冊頁的RATIONALE部分。

putenvsetenv都應該是 POSIX 兼容的。 如果您有包含putenv的代碼,並且代碼運行良好,請不要管它。 如果您正在開發新代碼,您可能需要考慮setenv

如果您想查看setenv ( stdlib/setenv.c ) 或putenv ( stdlib/putenv.c /putenv.c) 的實現示例,請查看glibc 源代碼

沒有特殊的“環境”空間——setenv 只是為字符串動態分配空間(例如malloc ),就像你通常做的那樣。 因為環境不包含其中每個字符串來自何處的任何指示,所以setenvunsetenv不可能釋放任何可能由先前調用 setenv 動態分配的空間。

“因為它不會復制傳遞的字符串,所以你不能用本地調用它,並且不能保證堆分配的字符串不會被覆蓋或意外刪除。” putenv 的目的是確保如果您有一個堆分配的字符串,則可以故意將其刪除。 這就是基本原理文本的含義,即“唯一可用於添加到環境中且不允許 memory 泄漏的 function”。 是的,您可以使用本地調用它,只需在從 function 返回之前從環境中刪除字符串( putenv("FOO=")或 unsetenv)。

關鍵是使用 putenv 使得從環境中刪除字符串的過程完全具有確定性。 而 setenv 將在某些現有實現上修改環境中的現有字符串,如果新值更短(以避免總是泄漏內存),並且由於它在您調用 setenv 時創建了副本,因此您無法控制最初動態分配的字符串所以當它被刪除時你不能釋放它。

同時,setenv本身(或 unsetenv)不能釋放前一個字符串,因為 - 即使忽略 putenv - 字符串可能來自原始環境,而不是由之前的 setenv 調用分配。

(整個答案假設一個正確實現的 putenv,即不是你提到的 glibc 2.0-2.1.1 中的那個。)

我強烈建議不要使用這些功能中的任何一個。 只要您小心,並且只有一部分代碼負責修改環境,任何一種可以安全且無泄漏地使用,但是如果任何代碼可能正在使用線程並可能讀取環境,則很難正確且危險(例如,用於時區、語言環境、dns 配置等目的)。

我能想到的修改環境的唯一兩個目的是在運行時更改時區,或者將修改后的環境傳遞給子進程。 對於前者,您可能必須使用其中一個功能( setenv / putenv ),或者您可以手動遍歷environ來更改它(如果您擔心其他線程可能會同時嘗試讀取環境,這可能會更安全) )。 對於后一種用途(子進程),請使用exec -family 函數之一,該函數可讓您指定自己的環境數組,或者簡單地破壞environ (全局)或在fork之后但在exec之前在子進程中使用setenv / putenv ,在在這種情況下,您不必關心內存泄漏或線程安全,因為沒有其他線程並且您即將破壞地址空間並用新的進程映像替換它。

此外(雖然我沒有測試過),因為環境變量的一種用途是將值傳遞給孩子的環境,如果孩子調用 exec() 函數之一,這似乎沒用。 我錯了嗎?

這不是環境傳遞給孩子的方式。 所有各種風格的exec() (您可以在手冊的第 3 節中找到,因為它們是庫函數)最終調用系統調用execve() (您可以在手冊的第 2 節中找到)。 arguments 是:

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

環境變量的向量是顯式傳遞的(並且可能部分由您的putenv()setenv()調用的結果構造)。 kernel 將這些復制到新進程的地址空間中。 從歷史上看,此副本的可用空間限制了您的環境大小(類似於參數限制),但我不熟悉現代 Linux kernel 的限制。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM