Возвращает указатель на статическую локальную переменную?
Я работаю с некоторым кодом, который широко использует идиому возврата указателя на статическую локальную переменную. например:
char* const GetString()
{
static char sTest[5];
strcpy(sTest, "Test");
return sTest;
}
Я правильно понимаю, что это безопасно?
PS, я знаю, что это будет лучший способ сделать то же самое:
char* const GetString()
{
return "Test";
}
Edit:
Извинения, подпись функции должна, конечно, быть:
const char* GetString();
Ответы
Ответ 1
Первый пример: несколько безопасный
char* const GetString()
{
static char sTest[5];
strcpy(sTest, "Test");
return sTest;
}
Хотя это и не рекомендуется, это безопасно, область действия статической переменной остается в живых, даже когда объем функции заканчивается. Эта функция не очень безопасна для потоков. Лучшая функция позволит вам передать char* buffer
и a maxsize
для функции GetString()
для заполнения.
В частности, эта функция не считается реентерабельной функцией, поскольку реентерабельные функции не должны, среди прочего, возвращать адрес статическим (глобальным) непостоянным данным, См. реентеративные функции.
Второй пример: полностью небезопасный
char* const GetString()
{
return "Test";
}
Это было бы безопасно, если вы сделали const char *
.
То, что вы дали, небезопасно. Причина в том, что строковые литералы могут быть сохранены в сегменте памяти только для чтения и позволяют им изменять, что приведет к результатам undefined.
char* const
(указатель const) означает, что вы не можете изменить адрес, на который указывает указатель. const char *
(указатель на const) означает, что вы не можете изменить элементы, на которые указывает этот указатель.
Вывод:
Вы должны рассмотреть либо:
1) Если у вас есть доступ к коду, измените GetString
, чтобы принять параметр char* buffer
для заполнения и maxsize
для использования.
2) Если у вас нет доступа к коду, но вы должны его вызвать, оберните этот метод в другую функцию, которая защищена мьютексом. Новый метод описан в 1.
Ответ 2
В принципе, да, это безопасно в том смысле, что значение будет длиться бесконечно, потому что оно статично.
Это небезопасно в том смысле, что вы вернули постоянный указатель на переменные данные, а не указатель переменной на постоянные данные. Лучше, если вызывающим функциям не разрешено изменять данные:
const char *GetString(void)
{
static char sTest[5];
strncpy(sTest, "Test", sizeof(sTest)-1);
sTest[sizeof(sTest)-1] = '\0';
return sTest;
}
В показанном простом случае вряд ли стоит беспокоиться о переполнении буфера, хотя моя версия кода вызывает беспокойство и обеспечивает нулевое завершение. Альтернативой было бы использовать TR24731 функцию strcpy_s
вместо:
const char *GetString(void)
{
static char sTest[5];
strcpy_s(sTest, sizeof(sTest), "Test");
return sTest;
}
Что еще более важно, оба варианта возвращают указатель (переменный) к постоянным данным, поэтому пользователь не должен изменять модификацию строки и (возможно) выталкивать вне диапазона массива. (Как отмечает @strager в комментариях, возврат const char *
не является гарантией того, что пользователь не будет пытаться модифицировать возвращаемые данные. Однако они должны отбросить возвращаемый указатель, чтобы он был неконстантным, а затем изменить данные, это вызывает поведение undefined, и в этот момент все возможно.)
Одно из преимуществ буквального возврата заключается в том, что обещание без записи обычно может выполняться компилятором и операционной системой. Строка будет помещена в сегмент текста (кода) программы, и операционная система будет генерировать ошибку (нарушение сегментации в Unix), если пользователь попытается изменить данные, на которые указывает возвращаемое значение.
[По крайней мере, один из других ответов отмечает, что код не является повторным; это верно. Версия, возвращающая литерал, является повторной попыткой. Если важно повторное подключение, интерфейс должен быть исправлен, чтобы вызывающий абонент предоставил пространство, в котором хранятся данные.]
Ответ 3
Это зависит от того, что вы подразумеваете под безопасностью. Есть несколько проблем, которые я вижу сразу:
- Вы вернули
char * const
, который позволит вызывающим абонентам изменять строку в этом месте. Потенциальный переполнение буфера. Или вы имели в виду const char *
?
- У вас может возникнуть проблема с повторной установкой или с concurrency.
Чтобы объяснить второе, рассмотрим следующее:
const char * const format_error_message(int err)
{
static char error_message[MAXLEN_ERROR_MESSAGE];
sprintf(error_message, "Error %#x occurred", err);
return error_message;
}
Если вы вызываете это так:
int a = do_something();
int b = do_something_else();
if (a != 0 && b != 0)
{
fprintf(stderr,
"do_something failed (%s) AND do_something_else failed (%s)\n",
format_error_message(a), format_error_message(b));
}
... что будет напечатано?
То же самое для потоковой передачи.
Ответ 4
static
переменные (в функции) подобны глобальным переменным. В общем, их следует избегать (например, глобальных переменных, они вызывают проблемы с повторным подключением), но иногда полезны (некоторые стандартные функции библиотеки используют их). Вы можете возвращать указатели на глобальные переменные, поэтому вы можете также возвращать указатели на переменные static
.
Ответ 5
Да, это совершенно безопасно. Время жизни локальной статики - это все исполнение программы в C. Поэтому вы можете вернуть указатель на нее, так как массив будет доступен даже после возвращения функции, а возвращаемый указатель может быть действительно отменен.
Ответ 6
Это очень полезно, так как вы можете использовать функцию непосредственно как параметр printf.
Но, как уже упоминалось, многократные вызовы функции внутри одного вызова вызовут проблему, потому что функция использует одно и то же хранилище, а вызов дважды переписывает возвращаемую строку. Но я тестировал этот кусок кода, и он, похоже, работает - вы можете безопасно вызвать функцию, где givemestring используется не чаще MAX_CALLS и будет вести себя корректно.
#define MAX_CALLS 3
#define MAX_LEN 30
char *givemestring(int num)
{
static char buf[MAX_CALLS][MAX_LEN];
static int rotate=0;
rotate++;
rotate%=sizeof(buf)/sizeof(buf[0]);
sprintf(buf[rotate],"%d",num);
return buf[rotate];
}
Единственная проблема - безопасность потоков, но это можно решить с помощью локальных переменных потока (gcc __thread keyword)
Ответ 7
Да, это часто используется для возврата части текста некоторого поиска, т.е. для перевода некоторого номера ошибки в дружественную человеку строку.
Его разумно сделать это в тех случаях, когда вы:
fprintf(stderr, "Error was %s\n", my_string_to_error(error_code));
Если my_string_to_error()
возвращает выделенную строку, ваша программа будет течь, учитывая указанное (очень) общее использование такой функции.
char const *foo_error(...)
{
return "Mary Poppins";
}
... тоже хорошо, некоторые компиляторы с мертвым мозгом могут захотеть, чтобы вы его набросили.
Просто смотрите строки таким образом, не возвращайте книгу:)
Ответ 8
Возвращающий указатель на локальную переменную, будь то статический или нет, потенциально небезопасен, поскольку срок службы статической переменной ограничен функцией, это будет обвисший указатель.