Ich habe ein wenig über Umgebungsvariablen nachgedacht und habe ein paar Fragen/Beobachtungen.
-
putenv(char *string);
Dieser Aufruf scheint fatal fehlerhaft. Da die übergebene Zeichenfolge nicht kopiert wird, können Sie sie nicht lokal aufrufen, und es gibt keine Garantie dafür, dass eine dem Heap zugewiesene Zeichenfolge nicht überschrieben oder versehentlich gelöscht wird. Darüber hinaus (obwohl ich es nicht getestet habe) erscheint dies nutzlos, da eine Verwendung von Umgebungsvariablen darin besteht, Werte an die Umgebung des Kindes zu übergeben, wenn das Kind eine der aufruft exec*()
Funktionen. Liege ich da falsch?
-
Die Linux-Manpage gibt an, dass glibc 2.0-2.1.1 das obige Verhalten aufgab und mit dem Kopieren der Zeichenfolge begann, aber dies führte zu einem Speicherleck, das in glibc 2.1.2 behoben wurde. Mir ist nicht klar, was dieses Speicherleck war oder wie es behoben wurde.
-
setenv()
kopiert die Zeichenfolge, aber ich weiß nicht genau, wie das funktioniert. Speicherplatz für die Umgebung wird zugewiesen, wenn der Prozess geladen wird, aber er ist behoben. Ist hier eine (willkürliche?) Konvention am Werk? Zum Beispiel mehr Slots im Env-String-Zeiger-Array zuweisen als derzeit verwendet und den Null-Ende-Zeiger nach Bedarf nach unten verschieben? Ist der Speicher für den neuen (kopierten) String im Adressraum der Umgebung selbst allokiert und wenn er zu groß ist, holt man sich einfach ENOMEM?
-
Gibt es in Anbetracht der oben genannten Probleme einen Grund, dies zu bevorzugen? putenv()
Über setenv()
?
- [The]
putenv(char *string);
[…] Aufruf scheint fatal fehlerhaft.
Ja, es ist fatal fehlerhaft. Es wurde in POSIX (1988) beibehalten, weil dies der Stand der Technik war. Das setenv()
Mechanismus kam später. Korrektur: Der POSIX 1990-Standard sagt in §B.4.6.1 „Zusätzliche Funktionen putenv() und clearenv() wurden in Betracht gezogen, aber abgelehnt”. Die Einzelne Unix-Spezifikation (SUS) Version 2 von 1997 listet auf putenv()
aber nicht setenv()
oder unsetenv()
. Die nächste Überarbeitung (2004) definierte beides setenv()
und unsetenv()
auch.
Da die übergebene Zeichenfolge nicht kopiert wird, können Sie sie nicht lokal aufrufen, und es gibt keine Garantie dafür, dass eine dem Heap zugewiesene Zeichenfolge nicht überschrieben oder versehentlich gelöscht wird.
Sie haben Recht, dass eine lokale Variable fast immer eine schlechte Wahl für die Übergabe ist putenv()
— die Ausnahmen sind so dunkel, dass sie fast nicht mehr existieren. Wenn der String auf dem Heap allokiert wird (mit malloc()
et al), müssen Sie sicherstellen, dass Ihr Code ihn nicht ändert. Wenn dies der Fall ist, verändert es gleichzeitig die Umgebung.
Darüber hinaus (obwohl ich es nicht getestet habe) erscheint dies nutzlos, da eine Verwendung von Umgebungsvariablen darin besteht, Werte an die Umgebung des Kindes zu übergeben, wenn das Kind eine der aufruft exec*()
Funktionen. Liege ich da falsch?
Das exec*()
Funktionen erstellen eine Kopie der Umgebung und übergeben diese an den ausgeführten Prozess. Da gibt es kein Problem.
Die Linux-Manpage gibt an, dass glibc 2.0-2.1.1 das obige Verhalten aufgab und mit dem Kopieren der Zeichenfolge begann, aber dies führte zu einem Speicherleck, das in glibc 2.1.2 behoben wurde. Mir ist nicht klar, was dieses Speicherleck war oder wie es behoben wurde.
Das Speicherleck entsteht, weil man einmal angerufen hat putenv()
mit einer Zeichenfolge können Sie diese Zeichenfolge für keinen Zweck erneut verwenden, da Sie nicht feststellen können, ob sie noch verwendet wird, obwohl Sie den Wert ändern könnten, indem Sie ihn überschreiben (mit unbestimmten Ergebnissen, wenn Sie den Namen in den einer Umgebungsvariablen ändern an anderer Stelle in der Umgebung gefunden). Also, wenn Sie Platz zugeteilt haben, der Klassiker putenv()
leckt es, wenn Sie die Variable erneut ändern. Wann putenv()
begann, Daten zu kopieren, zugewiesene Variablen wurden unreferenziert, weil putenv()
Es wurde kein Verweis mehr auf das Argument beibehalten, aber der Benutzer erwartete, dass die Umgebung darauf verweisen würde, sodass der Speicher verloren ging. Ich bin mir nicht sicher, was der Fix war – ich würde zu 3/4 erwarten, dass es zum alten Verhalten zurückkehrt.
setenv()
kopiert die Zeichenfolge, aber ich weiß nicht genau, wie das funktioniert. Speicherplatz für die Umgebung wird zugewiesen, wenn der Prozess geladen wird, aber er ist behoben.
Der ursprüngliche Umgebungsraum ist festgelegt; Wenn Sie anfangen, es zu ändern, ändern sich die Regeln. Sogar mit putenv()
wird die ursprüngliche Umgebung geändert und könnte durch das Hinzufügen neuer Variablen oder durch das Ändern vorhandener Variablen mit längeren Werten wachsen.
Ist hier eine (willkürliche?) Konvention am Werk? Zum Beispiel mehr Slots im Env-String-Zeiger-Array zuweisen als derzeit verwendet und den Null-Ende-Zeiger nach Bedarf nach unten verschieben?
Das ist, was die setenv()
Mechanismus ist wahrscheinlich zu tun. Die (globale) Variable environ
zeigt auf den Anfang des Arrays von Zeigern auf Umgebungsvariablen. Wenn es zu einem Zeitpunkt auf einen Speicherblock und zu einem anderen Zeitpunkt auf einen anderen Block zeigt, wird die Umgebung einfach so umgeschaltet.
Ist der Speicher für den neuen (kopierten) String im Adressraum der Umgebung selbst allokiert und wenn er zu groß ist, holt man sich einfach ENOMEM?
Nun, ja, Sie könnten ENOMEM bekommen, aber Sie müssten sich ziemlich anstrengen. Und wenn Sie die Umgebung zu groß machen, können Sie andere Programme möglicherweise nicht richtig ausführen – entweder wird die Umgebung abgeschnitten oder die Ausführungsoperation schlägt fehl.
Gibt es angesichts der oben genannten Probleme einen Grund, putenv() gegenüber setenv() zu bevorzugen?
- Verwenden
setenv()
im neuen Code.
- Aktualisieren Sie den alten Code, um ihn zu verwenden
setenv()
aber machen Sie es nicht zur obersten Priorität.
- Verwende nicht
putenv()
im neuen Code.
Es gibt keinen speziellen “Umgebungs”-Speicherplatz – setenv weist einfach dynamisch Speicherplatz für die Zeichenfolgen zu (mit malloc
zum Beispiel) wie Sie es normalerweise tun würden. Da die Umgebung keinen Hinweis darauf enthält, woher die einzelnen Zeichenfolgen stammen, ist dies unmöglich setenv
oder unsetenv
um Speicherplatz freizugeben, der möglicherweise durch vorherige Aufrufe von setenv dynamisch zugewiesen wurde.
“Da die übergebene Zeichenfolge nicht kopiert wird, können Sie sie nicht lokal aufrufen, und es gibt keine Garantie, dass eine dem Heap zugewiesene Zeichenfolge nicht überschrieben oder versehentlich gelöscht wird.” Der Zweck von putenv besteht darin, sicherzustellen, dass eine dem Heap zugewiesene Zeichenfolge gelöscht werden kann absichtlich. Das meint der Begründungstext mit „der einzigen verfügbaren Funktion, die der Umgebung hinzugefügt werden kann, ohne Speicherlecks zuzulassen“. Und ja, Sie können es mit einem lokalen aufrufen, entfernen Sie einfach die Zeichenfolge aus der Umgebung (putenv("FOO=")
oder unsetenv), bevor Sie von der Funktion zurückkehren.
Der Punkt ist, dass die Verwendung von putenv den Prozess des Entfernens einer Zeichenfolge aus der Umgebung vollständig deterministisch macht. Während setenv bei einigen vorhandenen Implementierungen eine vorhandene Zeichenfolge in der Umgebung ändert, wenn der neue Wert kürzer ist (um zu vermeiden stets Speicherverlust), und da beim Aufrufen von setenv eine Kopie erstellt wurde, haben Sie keine Kontrolle über die ursprünglich dynamisch zugewiesene Zeichenfolge, sodass Sie sie nicht freigeben können, wenn sie entfernt wird.
Inzwischen setenv selbst (oder unsetenv) kann die vorherige Zeichenfolge nicht freigeben, da die Zeichenfolge – selbst wenn Sie putenv ignorieren – möglicherweise aus der ursprünglichen Umgebung stammt, anstatt durch einen vorherigen Aufruf von setenv zugewiesen zu werden.
(Diese ganze Antwort setzt ein korrekt implementiertes Putenv voraus, dh nicht die in glibc 2.0-2.1.1, die Sie erwähnt haben.)
Lies das BEGRÜNDUNG Abschnitt der setenv
man-Seite von The Open Group Base Specifications Ausgabe 6.
putenv
und setenv
sollen beide POSIX-kompatibel sein. Wenn Sie Code mit haben putenv
darin, und der Code funktioniert gut, lassen Sie es in Ruhe. Wenn Sie neuen Code entwickeln, sollten Sie dies berücksichtigen setenv
.
Schaue auf die glibc-Quellcode wenn Sie ein Beispiel für eine Implementierung von sehen möchten setenv
(stdlib/setenv.c
) oder putenv
(stdlib/putenv.c
).
Darüber hinaus (obwohl ich es nicht getestet habe) erscheint dies nutzlos, da eine Verwendung von Umgebungsvariablen darin besteht, Werte an die Umgebung des Kindes zu übergeben, wenn das Kind eine der exec() -Funktionen aufruft. Liege ich da falsch?
So wird die Umwelt nicht an das Kind weitergegeben. All die verschiedenen Geschmacksrichtungen von exec()
(die Sie in Abschnitt 3 des Handbuchs finden, da es sich um Bibliotheksfunktionen handelt) rufen letztendlich den Systemaufruf auf execve()
(die Sie in Abschnitt 2 des Handbuchs finden). Die Argumente sind:
int execve(const char *filename, char *const argv[], char *const envp[]);
Der Vektor der Umgebungsvariablen wird explizit übergeben (und kann teilweise aus den Ergebnissen Ihrer putenv()
und setenv()
Anrufe). Der Kernel kopiert diese in den Adressraum des neuen Prozesses. In der Vergangenheit gab es eine Begrenzung der Größe Ihrer Umgebung, die sich aus dem für diese Kopie verfügbaren Speicherplatz ergab (ähnlich der Argumentbegrenzung), aber ich bin mit den Einschränkungen eines modernen Linux-Kernels nicht vertraut.
Ich würde dringend davon abraten, eine dieser Funktionen zu verwenden. Entweder kann sicher und ohne Lecks verwendet werden, solange Sie vorsichtig sind und nur ein Teil Ihres Codes für die Änderung der Umgebung verantwortlich ist, aber es ist schwer richtig zu machen und gefährlich, wenn irgendein Code Threads verwendet und die Umgebung lesen könnte (zB für Zeitzone, Gebietsschema, DNS-Konfiguration usw.).
Die einzigen zwei Zwecke, die mir zum Ändern der Umgebung einfallen, sind das Ändern der Zeitzone zur Laufzeit oder das Übergeben einer geänderten Umgebung an untergeordnete Prozesse. Für Ersteres müssen Sie wahrscheinlich eine dieser Funktionen verwenden (setenv
/putenv
), oder Sie könnten zu Fuß gehen environ
manuell zu ändern (this könnte sicherer sein, wenn Sie befürchten, andere Threads könnten gleichzeitig versuchen, die Umgebung zu lesen). Verwenden Sie für die letztere Verwendung (untergeordnete Prozesse) eine der exec
-Familienfunktionen, mit denen Sie Ihr eigenes Umgebungsarray angeben können, oder einfach clobber environ
(das globale) oder verwenden setenv
/putenv
im untergeordneten Prozess nach fork
Aber vorher exec
in diesem Fall müssen Sie sich nicht um Speicherlecks oder Thread-Sicherheit kümmern, da es keine anderen Threads gibt und Sie im Begriff sind, Ihren Adressraum zu zerstören und durch ein neues Prozessabbild zu ersetzen.