putenv()
. Она устанавливает переменную окружения для текущего процесса. В основном функция полезна, когда ваш процесс вызывает из под себя другой процесс и передает ему какие-либо параметры через переменные окружения. Прототип функции такой:
int putenv(char* string);
Казалось бы, все предельно просто и понятно вызываешь функцию, она делает свое дело, если надо проверятся код ошибки, и все пучком. Например:
...
putenv("TESTVAR=testvalue");
...
или так:...
char var[1024];
...
strcpy(var, "TESTVAR=");
strcat(var, value);
putenv(var);
...
Вроде похожие примеры, хотя между ними есть огромное "но". Первый пример будет работать нормально, а вот второй пример может приводить к неверному значению установленной переменной или даже к падению программы с нарушением защиты памяти на некоторых платформах. Почему? Отгадка кроется в том, что на некоторых операционных системах и версиях системной библиотекиСитуация усугубляется еще и тем, что разные версии системной библиотеки ведут себя по разному. Нет четкого правила: копировать значение буфера или нет. Я лично наступил на эти грабли, когда начал писать программу под виндами на Visual Studio, все работало. А вот при запуске на юниксах или под виндами, но использовании компилятораlibc
значение параметра будет использоваться напрямую даже после возврата из функции. Другими словами, функцияputenv()
не копирует переданный ей параметр куда-либо. Если вы передадите в эту функцию буфер, размещенный в стеке (автоматическая переменная), то это прямой путь к серьезным ошибкам памяти, так в момент реального использования установленной переменной автоматический буфер может уже не существовать (функция, его создавшая, уже завершила работу и подчистила за собой стек). Отсюда вывод указатель, передаваемый в функциюputenv()
, всегда должен указывать на статические данные. Первый пример работает правильно, так как в С и C++ строковые константы размещены в статическом сегменте и постоянны во время всей жизни программы.
gcc
из Cygwin, все падало. Конечно, чтение документации все сразу прояснило, но вот осадочек остался. Просто использование буфера напрямую, без создания внутренней копии, как-то противоречит здравому смыслу, но... как сделало, так сделано.Я набросал мини-тесты на всю эту тему.
Традиционно, для компиляции тестов нам нужна Google Test Framework. Как я уже писал, вы можете скачать мою модификацию этой библиотеки, которая сокращена до двух необходимых файловФайл тестовgtest/gtest.h
иgtest-all.cc
.
putenv_unittest.cpp
:#include <gtest/gtest.h>
#include <cstdlib>
// Главная проверочная функция. Вызывает putenv() с указанным буфером
// (автоматическим или статическим), если надо перезатирает буфер, чтобы
// проверить тот ли это буфер, и после сверяет установленное значение
// с ожидаемым эталоном.
void checkPutEnv(char* buf, bool rewrite) {
// Эталон
const char* etalon = "TESTVAR=testvalue";
// Копируем эталон в буфер
std::strcpy(buf, etalon);
// Устанавливаем переменную
putenv(buf);
// Если того требуют условия теста, то перезатираем буфер
// фальшивым значением.
if (rewrite)
std::strcpy(buf, "TESTVAR=novalue");
// Копируем значение системной переменной.
std::string actual = getenv("TESTVAR");
// Сверяем результаты
EXPECT_EQ(std::string("testvalue"), actual);
}
// Тест с использованием статического буфера без перезатирания.
// Это должно работать стопудово на все платформах, так как
// используется статический буфер, и мы его не перезатираем.
TEST(PutEnv, UsingStaticVariable) {
static char buf[1024];
checkPutEnv(buf, false);
}
// Тест с использованием статического буфера с перезатиранием.
// Данный тест будет рабоать только на системах, которые копируют
// аргумент putenv()'а во внутренний буфер. То есть перезатирание
// нами буфера не приведет к изменению значения установленной
// переменной.
TEST(PutEnv, UsingStaticVariableRewriteBuffer) {
static char buf[1024];
checkPutEnv(buf, true);
}
Файл головной программы запуска тестов:runner.cc
:#include "gtest/gtest.h"
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Сначала пробуем в Visual Studio.Компилируем:
cl /EHsc /I. /Feputenv_unittest_vs2008.exe runner.cpp putenv_unittest.cpp gtest\gtest-all.cc
Запускаем putenv_unittest_vs2008.exe
:[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from PutEnv
[ RUN ] PutEnv.UsingStaticVariable
[ OK ] PutEnv.UsingStaticVariable
[ RUN ] PutEnv.UsingStaticVariableRewriteBuffer
[ OK ] PutEnv.UsingStaticVariableRewriteBuffer
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran.
[ PASSED ] 2 tests.
Видно, что все тесты работают, а это значит, что микрософтовский putenv()
копирует значение аргумента во внутренний буфер, и значит можно передавать в него указатель на нестатический блок памяти.Теперь пробуем тоже под виндами, но через Cygwin.
Компилируем:
g++ -I. -o putenv_unittest_cygwin.exe putenv_unittest.cpp runner.cpp gtest/gtest-all.cc
Запускаем putenv_unittest_cygwin.exe
:[==========] Running 2 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 2 tests from PutEnv
[ RUN ] PutEnv.UsingStaticVariable
[ OK ] PutEnv.UsingStaticVariable
[ RUN ] PutEnv.UsingStaticVariableRewriteBuffer
putenv_unittest.cpp:15: Failure
Value of: actual
Actual: "novalue"
Expected: std::string("testvalue")
Which is: "testvalue"
[ FAILED ] PutEnv.UsingStaticVariableRewriteBuffer
[----------] Global test environment tear-down
[==========] 2 tests from 1 test case ran.
[ PASSED ] 1 test.
[ FAILED ] 1 test, listed below:
[ FAILED ] PutEnv.UsingStaticVariableRewriteBuffer
1 FAILED TEST
Видно, что тест PutEnv.UsingStaticVariableRewriteBuffer
, ожидая получить значение переменной testvalue
, получил novalue
. Ясно, мы принудительно перезаписали оригинальное значение переменной прямо в буфере после вызова putenv()
. Вывод: Cygwin'овский putenv()
не копирует значение аргумента куда-либо и, значение буфера используется напрямую, поэтому тут надо передавать указатель обязательно на статический блок памяти, или хотя бы такой блок, которые не исчезнет на момент обращения к переменной.Резюме.
Будьте осторожны при использовании функции
putenv()
, так как на некоторых платформах ее аргумент не копируется во внутренний буфер, как бы это ни подсказывала вам очевидная логика, а используется прямо из вашего буфера.Другие посты по теме:
Комментариев нет:
Отправить комментарий