- [The]
putenv(char *string);
[...] コールには致命的な欠陥があるようです。
はい、致命的な欠陥です。 POSIX (1988) に保存されたのは、それが先行技術だったからです。setenv()
メカニズムは後で到着しました。 訂正: POSIX 1990 標準は、§B.4.6.1 で「追加関数putenv()およびclearenv()は考慮されましたが拒否されました」と述べています。1997 年のSingle Unix Specificationputenv()
(SUS) バージョン 2 にはリストがありますが、setenv()
またははありませんunsetenv()
。次の改訂 (2004 年) では、setenv()
との両方が定義unsetenv()
されました。
渡された文字列をコピーしないため、ローカルで呼び出すことはできず、ヒープに割り当てられた文字列が上書きされたり誤って削除されたりしないという保証はありません。
おっしゃる通り、ほとんどの場合、ローカル変数を渡すのは適切ではありputenv()
ません。例外は、ほとんど存在しないという点であいまいです。文字列が (et al を使用して) ヒープに割り当てられている場合はmalloc()
、コードがそれを変更しないようにする必要があります。もしそうなら、それは同時に環境を変更しています。
さらに (私はテストしていませんが)、環境変数の 1 つの用途は子の環境に値を渡すことであるため、子がexec*()
関数の 1 つを呼び出すと、これは役に立たないように見えます。私はそれで間違っていますか?
関数は環境のexec*()
コピーを作成し、それを実行されたプロセスに渡します。そこは問題ありません。
Linux のマニュアル ページは、glibc 2.0-2.1.1 が上記の動作を放棄し、文字列のコピーを開始したことを示していますが、これによりメモリ リークが発生し、glibc 2.1.2 で修正されました。このメモリリークが何であるか、またはどのように修正されたかは、私には明らかではありません。
メモリ リークが発生するのは、一度putenv()
文字列を呼び出した後は、その文字列がまだ使用されているかどうかがわからないため、その文字列を再度使用することはできませんが、値を上書きして値を変更することはできます (環境内の別の位置にある環境変数の名前に変更します)。したがって、スペースを割り当てputenv()
た場合、変数を再度変更すると、クラシックはそれをリークします。データのコピーを開始すると、引数への参照が保持されなくなっputenv()
たため、割り当てられた変数が参照されなくなりましたが、ユーザーは環境がそれを参照していると予想したため、メモリ リークが発生しました。putenv()
修正が何であったかはわかりません — 4 分の 3 は、以前の動作に戻ることを期待していました。
setenv()
文字列をコピーしますが、それがどのように機能するか正確にはわかりません。プロセスのロード時に環境用のスペースが割り当てられますが、固定されています。
元の環境空間は固定されています。変更を開始すると、ルールが変更されます。を使用してもputenv()
、新しい変数を追加した結果、または既存の変数をより長い値を持つように変更した結果、元の環境が変更され、拡張される可能性があります。
ここで何らかの (恣意的な?) 規則が働いていますか? たとえば、現在使用されているよりも多くのスロットを env 文字列ポインター配列に割り当て、必要に応じてヌル終了ポインターを下に移動しますか?
それがsetenv()
メカニズムが行う可能性が高いことです。(グローバル) 変数environ
は、環境変数へのポインターの配列の先頭を指します。ある時点でメモリの 1 つのブロックを指し、別の時点で別のブロックを指している場合、環境はそのように切り替えられます。
新しい (コピーされた) 文字列のメモリは、環境自体のアドレス空間に割り当てられていますか? それが大きすぎて収まらない場合は、ENOMEM を取得するだけですか?
はい、ENOMEM を取得できますが、かなりの努力が必要です。また、環境が大きくなりすぎると、他のプログラムを適切に実行できなくなる可能性があります。環境が切り捨てられるか、実行操作が失敗します。
上記の問題を考慮して、setenv() よりも putenv() を好む理由はありますか?
setenv()
新しいコードで使用します。
- を使用するように古いコードを更新します
setenv()
が、それを最優先事項にしないでください。
putenv()
新しいコードでは使用しないでください。