web-dev-qa-db-ja.com

putenv()およびsetenv()に関する質問

私は環境変数について少し考えており、いくつかの質問/観察があります。

  • putenv(char *string);

    この呼び出しには致命的な欠陥があるようです。渡された文字列はコピーされないため、ローカルで呼び出すことはできません。ヒープに割り当てられた文字列が上書きされたり誤って削除されたりすることはありません。さらに(私はテストしていませんが)、環境変数の1つの用途は値を子の環境に渡すことであるため、子がexec*()関数の1つを呼び出す場合、これは役に立たないようです。私はそれで間違っていますか?

  • Linuxのmanページには、glibc 2.0-2.1.1が上記の動作を放棄して文字列のコピーを開始したことが示されていますが、これによりglibc 2.1.2で修正されたメモリリークが発生しました。このメモリリークが何であったか、またはどのように修正されたかは、私にはわかりません。

  • setenv()は文字列をコピーしますが、それがどのように機能するか正確にはわかりません。環境のスペースは、プロセスのロード時に割り当てられますが、固定されています。ここで(任意の)慣習が働いていますか?たとえば、現在使用されているよりも多くのスロットをenv文字列ポインター配列に割り当て、必要に応じてnull終了ポインターを下に移動しますか?新しい(コピーされた)文字列のメモリは環境自体のアドレス空間に割り当てられていますか?それが大きすぎて収まらない場合は、ENOMEMを取得しますか?

  • 上記の問題を考慮して、putenv()よりsetenv()を優先する理由はありますか?

38
ValenceElectron
  • [The] putenv(char *string); [...]呼び出しには致命的な欠陥があるようです。

はい、致命的な欠陥があります。 それは先行技術だったので、POSIX(1988)で保存されました。 setenv()メカニズムは後で到着しました。 修正:POSIX 1990標準では、§B.4.6.1で「追加関数putenv()およびclearenv()は考慮されましたが拒否されました。 Single Unix Specification (SUS)バージョン2 1997には、putenv()がリストされていますが、setenv()またはunsetenv()はリストされていません。次のリビジョン(2004)では、 setenv()unsetenv() の両方が定義されました。

渡された文字列はコピーされないため、ローカルで呼び出すことはできません。ヒープに割り当てられた文字列が上書きされたり誤って削除されたりすることはありません。

ローカル変数は、ほぼ常にputenv()に渡すのに悪い選択です—例外はほとんど存在しないという点で不明瞭です。文字列がヒープに割り当てられている場合(malloc() et alを使用)、コードがそれを変更しないようにする必要があります。含まれている場合は、同時に環境を変更しています。

さらに(私はテストしていませんが)、環境変数の1つの用途は値を子の環境に渡すことであるため、子がexec*()関数の1つを呼び出す場合、これは役に立たないようです。私はそれで間違っていますか?

exec*()関数は、環境のコピーを作成し、それを実行されたプロセスに渡します。そこに問題はありません。

Linuxのmanページには、glibc 2.0-2.1.1が上記の動作を放棄して文字列のコピーを開始したことが示されていますが、これによりglibc 2.1.2で修正されたメモリリークが発生しました。このメモリリークが何であったか、またはどのように修正されたかは、私にはわかりません。

文字列でputenv()を呼び出すと、上書きして値を変更することはできますが、その文字列がまだ使用中であるかどうかわからないため、その文字列をいかなる目的にも再び使用できないため、メモリリークが発生します。それ(環境内の別の位置にある環境変数の名前に名前を変更した場合の結果は不確定です)。したがって、スペースを割り当てた場合、変数を再度変更すると、従来のputenv()はそれをリークします。 putenv()がデータのコピーを開始すると、putenv()が引数への参照を保持しなくなったため、割り当てられた変数が参照されなくなりましたが、ユーザーが環境がそれを参照すると予想していたため、メモリは漏れた。修正が何であったかはわかりません— 3/4は、以前の動作に戻ることになると思います。

setenv()は文字列をコピーしますが、それがどのように機能するか正確にはわかりません。環境のスペースは、プロセスのロード時に割り当てられますが、固定されています。

元の環境空間は固定されています。変更を開始すると、ルールが変更されます。 putenv()を使用した場合でも、元の環境は変更され、新しい変数を追加した結果、または既存の変数を変更してより長い値にした結果、大きくなる可能性があります。

ここで(任意の)慣習が働いていますか?たとえば、現在使用されているよりも多くのスロットをenv文字列ポインター配列に割り当て、必要に応じてnull終了ポインターを下に移動しますか?

それがsetenv()メカニズムが行う可能性が高いことです。 (グローバル)変数environは、環境変数へのポインターの配列の先頭を指します。一度に1つのメモリブロックを指し、別の時間に別のブロックを指す場合、そのように環境が切り替わります。

新しい(コピーされた)文字列のメモリは環境自体のアドレス空間に割り当てられていますか?それが大きすぎて収まらない場合は、ENOMEMを取得しますか?

ええ、ええ、ENOMEMを入手することはできますが、かなり一生懸命努力する必要があります。また、環境を大きくしすぎると、他のプログラムを適切に実行できなくなる可能性があります。環境が切り捨てられるか、実行操作が失敗します。

上記の問題を考慮して、setenv()よりputenv()を優先する理由はありますか?

  • 新しいコードでsetenv()を使用します。
  • setenv()を使用するように古いコードを更新しますが、最優先にしないでください。
  • 新しいコードではputenv()を使用しないでください。
40

特別な「環境」スペースはありません。setenvは、通常のように(たとえば、mallocを使用して)文字列に動的にスペースを割り当てます。環境には、各文字列がどこから来たかの表示が含まれていないため、setenvまたはunsetenvが、setenvへの以前の呼び出しによって動的に割り当てられた可能性があるスペースを解放することは不可能です。

「渡された文字列はコピーされないため、ローカルで呼び出すことはできません。ヒープに割り当てられた文字列が上書きされたり誤って削除されたりすることはありません。」 putenvの目的は、ヒープに割り当てられた文字列がある場合、それを削除できるようにすることです意図的に。これが、「メモリリークを許可せずに環境に追加できる唯一の機能」という意味のテキストの意味です。そして、はい、ローカルから呼び出すことができます。関数から戻る前に、環境から文字列を削除するだけです(putenv("FOO=")またはunsetenv)。

ポイントは、putenvを使用すると、環境から文字列を削除するプロセスが完全に確定的になることです。一方、setenvは一部の既存の実装で、新しい値が短い場合(alwaysメモリのリークを回避するため)、環境内の既存の文字列を変更します。また、setenvを呼び出すとコピーが作成されるため、元々動的に割り当てられた文字列を制御するため、削除されたときに解放できません。

一方、setenv itself(またはunsetenv)は以前の文字列を解放できません。これは、putenvを無視しても、文字列が以前のsetenvの呼び出しによって割り当てられたのではなく、元の環境からのものである可能性があるためです。

(この全体の答えは、正しく実装されたputenv、つまりnotがglibc 2.0-2.1.1にあるものであることを前提としています。)

5
Random832

Open Group Base Specifications Issue 6のsetenv manページの [〜#〜] rationale [〜#〜] セクションをお読みください。

putenvsetenvはどちらもPOSIXに準拠しているはずです。 putenvが含まれるコードがあり、コードが適切に機能する場合は、そのままにしておきます。新しいコードを開発している場合は、setenvを検討してください。

setenvstdlib/setenv.c )の実装例を確認するには、 glibcソースコード を参照してください。またはputenvstdlib/putenv.c )。

5
jim mcnamara

これらの機能のどちらも使用しないことを強くお勧めします。注意深く、コードの一部のみが環境の変更を担当している限り、canを安全かつリークなしで使用できますが、コードが適切である可能性がある場合、正しく取得して危険にすることは困難です。スレッドを使用し、環境を読み取る場合があります(タイムゾーン、ロケール、dns構成などの目的で)。

環境を変更するために考えられる唯一の2つの目的は、実行時にタイムゾーンを変更すること、または変更した環境を子プロセスに渡すことです。前者については、おそらくこれらの関数の1つ(setenv/putenv)を使用する必要があります。または、手動でenvironをウォークして変更することもできます(これはmight他のスレッドが同時に環境を読み取ろうとするのではないかと心配している場合は安全です。後者の使用(子プロセス)の場合は、独自の環境配列を指定できるexec- family関数の1つを使用するか、単にenviron(グローバル)を使用するか、setenv/putenv子プロセスのforkの後、execの前。この場合、メモリリークやスレッドセーフ性について気にする必要はありません。他のスレッドがなく、アドレススペースを破棄して、新しいプロセスイメージに置き換えようとしている。

さらに(私はテストしていませんが)、環境変数の1つの用途は値を子の環境に渡すことであるため、子がexec()関数の1つを呼び出す場合、これは役に立たないようです。私はそれで間違っていますか?

それは環境が子供に渡される方法ではありません。 exec()のさまざまなフレーバー(ライブラリ関数であるため、マニュアルのセクション3にあります)はすべて、最終的にシステムコールexecve()(セクション2にあります)を呼び出します。マニュアル)。引数は次のとおりです。

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

環境変数のベクトルは明示的に渡されます(putenv()およびsetenv()呼び出しの結果から部分的に構築される場合があります)。カーネルはこれらを新しいプロセスのアドレス空間にコピーします。歴史的には、このコピーに使用できるスペースから派生した環境のサイズには制限がありました(引数の制限と同様)が、最近のLinuxカーネルの制限についてはよく知りません。

4
Ben Jackson