すでに存在する環境変数に putenv() を使用すると、メモリはどうなりますか?

すでに存在する環境変数に putenv() を使用すると、メモリはどうなりますか?

環境は本質的に文字列へのポインタ配列であることを理解しています。したがって、これらの文字列が占めるメモリを解放すると、その環境変数が失われます。

環境文字列にメモリを割り当て、それを使用してputenv()既存の変数を設定すると、メモリリークが発生しますか?

次のCプログラムで示されています。

char ** environ;

main()
{
  /* 
     Code for printing all environment variables
  */ 

  char * temp = (char *)malloc( 64 * sizeof(char));
  strcat(temp, "");
  strcat(temp, "PWD=/home/mycomputer/");
  putenv(temp);

  /*
     Code for printing all environment variables 
  */
}

このPWD環境変数はすでにいくつかのメモリ(パスを含む)を指しており、現在より多くのメモリ(パスを含む)を割り当てました。したがって、ポインタが新しい場所に移動し、古いメモリにアクセスすることはできませんが、それでも割り当てられます。それでは、プログラムにメモリリークがあると言うのは正しいですか?そうでない場合、この変更はメモリにどのように受け入れられますか?

答え1

あなたの観察は正しいです。割り当てた新しい文字列はmalloc古い文字列を置き換え、古い文字列は廃止されました。前の文字列は、プログラムの起動時に渡された環境の一部である場合はスタックセグメントの一部です(つまり、プロセスで変数が最初に変更された場合)。

これがメモリリークかどうかは議論の余地があります。以前の文字列が占有していたメモリを解放することはできませんが、そのメモリを再利用するための合理的な方法はありません。putenv複数回呼び出すと、free文字列が使用されなくなったときに自己割り当てメモリを使用できます。

putenvドキュメントに記載されているように、glibcのバージョンによって意味が異なります。マニュアルページ

ところで、代わりにプログラムにバグがあります

strcat(temp, "");
strcat(temp, "PWD=/home/mycomputer/");

あなたは書かなければなりません

strcpy(temp, "PWD=/home/mycomputer/");

答え2

環境は3つの部分で構成されています。

  1. envpのようなデュアルポインタと呼ばれますenvironment list pointer

  2. を指してenvironment list、環境文字列を指すポインタ配列と考えることができます。

  3. environment stringsフォームname=value

これで、プロセスの仮想マシンレイアウトでどこに保存されますか?

答えは - スタックから呼び出すことができます。より高い住所

一つあるコンクリートの天井とコンクリートの床;上記ではレイアウトの天井なので拡張できません。以下ではスタックのため拡張できません。

今あなたの質問に答えるために -

内部的には、文字列をより高いセグメントの長さより大きい値に変更するときにこれを行いますmalloc

これにより、環境リストのポインタが割り当てられたメモリを指すように変更されます。 したがって、一部のポインタがまだより高いメモリのアドレスを指す環境のリストがあり、この特定のポインタはヒープの一部の領域を指します。。この特別なケースでは改訂するパラメーターの環境リスト全体はヒープにコピーされません。

ポインタが指す古いメモリはどうなりますか?まあそうだメモリリークに似ていますが、通常はそうではありません。方法を提供、そのメモリはすでに存在し、今は役に立たないブロックなので、これ以上指すことはできません。

動的割り当てではなく、すでに予約されているセグメントの一部である静的割り当てであるため、メモリリークとは呼ばれません。

メモリリークは、メモリの一部が他の目的に使用できますがアクセスできないため使用できないように聞こえます。ただし、いかなる場合でも、親アドレスにあるこのメモリーの断片は、そもそも他の目的には使用できません。

したがって、メモリリークのように見えますが、一般的にはそう呼ばれません。

混乱させて申し訳ありません。できるだけ明確に説明しようとしました:)

答え3

最初のバージョンで質問を修正したのは残念ですsetenv()setenv() する少なくともglibcの実装では、メモリリークが発生します。

たとえば、次のサンプルプログラムを使用すると、メモリが不足するまで大きくなり続けます。

#include <stdlib.h>
#include <stdio.h>
int main(void){
        char buf[24];
        for(;;){
                snprintf(buf, sizeof buf, "%d", rand());
                setenv("foo", buf, 1);
        }
}

glibcの実装では、setenv()以前に割り当てられたメモリは解放されませんsetenv()。ただし、バイナリツリーで割り当てられた環境文字列を追跡して重複を防ぎます(tfind(3)リストとは別に使用char **environ)。これは漏れのような道具を隠す良い効果もありますvalgrind;-)


これに関しては、putenv()引数として渡された文字列を管理するために呼び出し元に依存します。したがって、呼び出し側は、繰り返し呼び出し時にリークしないか、putenv()環境の一部である間にリリースされないか、自動/スタック変数を使用しないことを確認する必要があります。

また、文字列を呼び出して変更すると、putenv()環境変数が削除されたり、他の環境変数が追加される可能性があります。例:

#define _XOPEN_SOURCE   500
#include <stdlib.h>
#include <stdio.h>
#include <err.h>
#include <unistd.h>
int main(void){
        extern char **environ;
        static char *my_environ[] = { "YUCK=yumm", 0 };
        static char str[256] = "FOO=bar";
        environ = my_environ;
        putenv(str);
        snprintf(str, sizeof str, "BAZ=quux");
        execl("/usr/bin/printenv", "printenv", (void*)0);
        err(1, "execlp");
}

これは標準で必要ですが、putenv()引数を複製する古いバージョンのglibcでは機能しません。


最後に、環境に新しい変数が追加されると、配列はsetenv()すべてputenv()再配置されます。char **environ実装はポインタの独自のコピーを保持して拡張しますrealloc()が、元のポインタ(静的メモリを指す)を破損したり、手動で割り当てられたメモリを使用したりしません。environ = calloc(sizeof *environ, envlen)

関連情報