Подтвердить что ты не робот

Вопросы о putenv() и setenv()

Я немного подумывал о переменных окружения и имею несколько вопросов/наблюдений.

  • putenv(char *string);

    Этот вызов кажется смертельно ошибочным. Поскольку он не копирует переданную строку, вы не можете называть ее локальным, и нет никакой гарантии, что выделенная кучей строка не будет перезаписана или случайно удалена. Кроме того (хотя я его не тестировал), поскольку одно использование переменных среды - это передача значений в дочернюю среду, это кажется бесполезным, если ребенок вызывает одну из функций exec*(). В чем я ошибаюсь?

  • Персональная страница Linux указывает, что glibc 2.0-2.1.1 отказался от вышеуказанного поведения и начал копировать строку, но это привело к утечке памяти, которая была исправлена ​​в glibc 2.1.2. Мне непонятно, что такое утечка памяти и как она была исправлена.

  • setenv() копирует строку, но я точно не знаю, как это работает. Пространство для среды выделяется при загрузке процесса, но оно фиксируется. Существует ли какое-то (произвольное?) Соглашение? Например, выделение большего количества слотов в массиве указателей строки env, чем в настоящее время, и перемещение нулевого ограничивающего указателя вниз по мере необходимости? Является ли память для новой (скопированной) строки, выделенной в адресном пространстве самой среды, и если она слишком велика, чтобы соответствовать вам, просто получите ENOMEM?

  • Учитывая вышеизложенные проблемы, есть ли основания предпочесть putenv() над setenv()?

4b9b3361

Ответ 1

  • [The] putenv(char *string); [...] вызов кажется смертельно ущербным.

Да, это смертельно опасно. Это было сохранено в POSIX (1988), потому что это был предшествующий уровень техники. Механизм setenv() появился позже. Исправление: Стандарт POSIX 1990 говорит в § B.4.6.1 "Дополнительные функции putenv() и clearenv() были рассмотрены, но отклонены". В версии 2 спецификации Unix (SUS) 1997 года перечислены функции putenv() но не setenv() или unsetenv(). Следующая редакция (2004) также определила как setenv() и unsetenv().

Поскольку он не копирует переданную строку, вы не можете вызвать ее с локальным именем, и нет гарантии, что выделенная куча строка не будет перезаписана или случайно удалена.

Вы правы, что локальная переменная почти всегда является плохим выбором для передачи в putenv() - исключения неясны до такой степени, что почти не существуют. Если строка размещена в куче (с помощью malloc() и др.), Вы должны убедиться, что ваш код не изменяет ее. Если это так, то он изменяет среду одновременно.

Кроме того (хотя я не проверял это), поскольку одно из применений переменных среды - передача значений в дочернюю среду, это кажется бесполезным, если дочерний процесс вызывает одну из функций exec*(). Я не прав в этом?

Функции exec*() создают копию окружения и передают ее исполняемому процессу. Там нет проблем там.

Справочная страница Linux указывает, что glibc 2.0-2.1.1 отказался от вышеуказанного поведения и начал копировать строку, но это привело к утечке памяти, которая была исправлена в glibc 2.1.2. Мне не ясно, что это за утечка памяти или как ее исправить.

Утечка памяти возникает из-за того, что после того, как вы вызвали putenv() со строкой, вы не сможете снова использовать эту строку для каких-либо целей, потому что не можете определить, используется ли она по-прежнему, хотя вы можете изменить значение, перезаписав его (с неопределенным значением). результаты, если вы измените имя на имя переменной среды, найденной в другой позиции в среде). Итак, если вы выделили пространство, классическая putenv() его, если вы снова измените переменную. Когда putenv() начал копировать данные, выделенные переменные putenv() потому что putenv() больше не сохранял ссылку на аргумент, но пользователь ожидал, что среда будет ссылаться на него, поэтому память просочилась. Я не уверен, что это было за исправление - я бы ожидал, что оно вернется к старому поведению.

setenv() копирует строку, но я не знаю точно, как это работает. Пространство для среды выделяется при загрузке процесса, но оно фиксировано.

Исходное пространство среды фиксировано; когда вы начинаете изменять его, правила меняются. Даже с помощью putenv() исходная среда изменяется и может расти в результате добавления новых переменных или в результате изменения существующих переменных для получения более длинных значений.

Есть ли здесь какое-то (произвольное?) Соглашение? Например, выделение большего количества слотов в массиве указателей строки env, чем используется в настоящее время, и перемещение нулевого завершающего указателя вниз по мере необходимости?

Это то, что механизм setenv() может сделать. (Глобальная) переменная environ указывает на начало массива указателей на переменные окружения. Если он указывает на один блок памяти одновременно и на другой блок в другое время, то среда переключается, вот так.

Выделена ли память для новой (скопированной) строки в адресном пространстве самой среды, и если она слишком велика, чтобы соответствовать размеру, просто получите ENOMEM?

Ну, да, вы могли бы получить ENOMEM, но вам придется очень стараться. А если вы увеличите размер среды, вы не сможете правильно выполнить другие программы - среда будет усечена или операция exec завершится неудачно.

Учитывая вышеизложенные проблемы, есть ли причина предпочитать putenv(), а не setenv()?

  • Используйте setenv() в новом коде.
  • Обновите старый код, чтобы использовать setenv(), но не делайте его главным приоритетом.
  • Не используйте putenv() в новом коде.

Ответ 2

Прочитайте раздел ОБОСНОВАНИЕ справочной страницы setenv в разделе "Основные функции базы данных Open Group". 6.

putenv и setenv оба должны быть совместимы с POSIX. Если у вас есть код с putenv, и код работает хорошо, оставьте его в покое. Если вы разрабатываете новый код, вы можете рассмотреть setenv.

Посмотрите glibc исходный код, если вы хотите увидеть пример реализации setenv (stdlib/setenv.c) или putenv (stdlib/putenv.c).

Ответ 3

Не существует специального пространства "среда" - setenv просто динамически выделяет пространство для строк (например, malloc), как это обычно делалось бы. Поскольку среда не содержит никаких указаний на то, откуда взялась каждая строка, невозможно, чтобы setenv или unsetenv освобождало любое пространство, которое может быть динамически распределено предыдущими вызовами setenv.

"Поскольку он не копирует переданную строку, вы не можете называть ее локальным, и нет гарантии, что выделенная строка кучи не будет перезаписана или случайно удалена." Цель putenv состоит в том, чтобы убедиться, что если у вас есть выделенная кучей строка, ее можно удалить целиком. Это то, что текст обоснования означает "единственная функция, доступная для добавления в среду без разрешения утечек памяти". И да, вы можете называть его локальным, просто удалите строку из среды (putenv("FOO=") или unsetenv), прежде чем вы вернетесь из функции.

Дело в том, что использование putenv делает процесс удаления строки из полностью детерминированной среды. В то время как setenv будет в некоторых существующих реализациях модифицировать существующую строку в среде, если новое значение короче (чтобы избежать утечки памяти), и поскольку она создала копию при вызове setenv, вы не контролируете первоначально динамически выделенную строку поэтому вы не можете освободить его при его удалении.

Между тем, сам setenv (или unsetenv) не может освободить предыдущую строку, поскольку - даже игнорируя putenv - строка, возможно, исходила из исходной среды вместо того, чтобы выделяться предыдущим вызовом setenv.

(Весь этот ответ предполагает корректно реализованный putenv, т.е. не тот, о котором вы говорили в glibc 2.0-2.1.1.)

Ответ 4

Кроме того (хотя я его не тестировал), поскольку одно использование переменных среды - это передача значений дочерней среде, это кажется бесполезным, если ребенок вызывает одну из функций exec(). Я в этом не прав?

Это не то, как среда передается ребенку. Все различные варианты exec() (которые вы найдете в разделе 3 руководства, поскольку они являются библиотечными функциями) в конечном счете вызывают системный вызов execve() (который вы найдете в разделе 2 руководства). Аргументы:

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

Вектор переменных среды передается явно (и может быть частично построен из результатов ваших вызовов putenv() и setenv()). Ядро копирует их в адресное пространство нового процесса. Исторически существовал предел размера вашей среды, полученного из пространства, доступного для этой копии (аналогично пределу аргумента), но я не знаком с ограничениями на современное ядро ​​Linux.

Ответ 5

Я бы настоятельно рекомендовал использовать любую из этих функций. Либо можно использовать безопасно и без утечек, если вы будете осторожны, и только одна часть вашего кода отвечает за изменение среды, но трудно получить правильное и опасное, если какой-либо код может использовать потоки и может читать среду (например, для часового пояса, локали, конфигурации dns и т.д.).

Единственными целями, которые я могу придумать для изменения среды, является изменение часового пояса во время выполнения или передача измененной среды дочерним процессам. Для первого вам, вероятно, придется использовать одну из этих функций (setenv/putenv), или вы можете передвигаться environ вручную, чтобы изменить ее (это может быть безопаснее, если вы беспокоитесь, что другие потоки могут попытаться прочитать окружающая среда в то же время). Для последнего использования (дочерние процессы) используйте одну из функций exec -family, которая позволяет указать собственный массив окружения или просто clobber environ (глобальный) или использовать setenv/putenv в дочернем процесс после fork, но до exec, и в этом случае вам не нужно заботиться о утечке памяти или потоковой безопасности, потому что нет других потоков, и вы собираетесь уничтожить свое адресное пространство и заменить его на новый образ процесса.