Безопасен ли возврат указателя на статическую локальную переменную? - PullRequest
41 голосов
/ 17 января 2009

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

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}

Правильно ли я считаю, что это безопасно?

PS, я знаю, что это был бы лучший способ сделать то же самое:

char* const GetString()
{
  return "Test";
}

Edit: Извините, подпись функции должна, конечно, быть:

const char* GetString();

Ответы [ 7 ]

36 голосов
/ 17 января 2009

Первый пример: в некоторой степени безопасно

char* const GetString()
{
  static char sTest[5];
  strcpy(sTest, "Test");
  return sTest;
}

Хотя это и не рекомендуется, но это безопасно, область действия статической переменной остается активной, даже когда область действия функции заканчивается. Эта функция не очень поточно-ориентированная. Лучшая функция заставит вас передать char* buffer и maxsize для заполнения функции GetString().

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

Второй пример: совершенно небезопасно

char* const GetString()
{
  return "Test";
}

Это было бы безопасно, если бы вы сделали const char *. То, что вы дали, не безопасно. Причина в том, что строковые литералы могут храниться в сегменте памяти, доступной только для чтения, и возможность их изменения приведет к неопределенным результатам.

char* const (постоянный указатель) означает, что вы не можете изменить адрес, на который указывает указатель. const char * (указатель на const) означает, что вы не можете изменить элементы, на которые указывает этот указатель.

Вывод:

Вы должны рассмотреть либо:

1) Если у вас есть доступ к коду, то измените GetString, чтобы принять параметр char* buffer для заполнения и maxsize для использования.

2) Если у вас нет доступа к коду, но вы должны вызвать его, оберните этот метод в другую функцию, защищенную мьютексом. Новый метод, как описано в 1.

9 голосов
/ 17 января 2009

По сути, да, это безопасно в том смысле, что значение будет длиться бесконечно, потому что оно статично.

Это небезопасно в том смысле, что вы вернули постоянный указатель на переменные данные, а не переменный указатель на постоянные данные. Лучше, если вызывающим функциям не разрешено изменять данные:

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 * не является гарантией того, что пользователь не будет пытаться изменить возвращенные данные. Однако ему необходимо привести приведенный указатель, чтобы он не был константным, а затем изменить данные, это вызывает неопределенное поведение, и в этот момент возможно все.)

Одним из преимуществ буквального возврата является то, что обещание не писать обычно может быть применено компилятором и операционной системой. Строка будет помещена в текстовый (программный) сегмент программы, и операционная система создаст ошибку (нарушение сегментации в Unix), если пользователь попытается изменить данные, на которые указывает возвращаемое значение.

[По крайней мере, один из других ответов отмечает, что код не является повторным входом; это правильно. Версия, возвращающая литерал, является вновь поступающей. Если важен повторный вход, интерфейс должен быть исправлен так, чтобы вызывающая сторона предоставляла место, где хранятся данные.]

8 голосов
/ 17 января 2009

Это зависит от того, что вы подразумеваете под сейфом. Есть пара проблем, которые я вижу сразу:

  1. Вы вернули char * const, что позволит вызывающим абонентам изменять строку в этом месте. Потенциальный переполнение буфера. Или вы имели в виду const char *?
  2. У вас могут быть проблемы с входом или с параллелизмом.

Чтобы объяснить второе, рассмотрим это:

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));
} 

... что будет напечатано?

То же самое для потоков.

8 голосов
/ 17 января 2009

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

2 голосов
/ 17 января 2009

Да, это совершенно безопасно. Время жизни локальной статики - это время выполнения всей программы на C. Таким образом, вы можете вернуть указатель на него, так как массив будет живым даже после возврата из функции, и на возвращенный указатель можно будет дать правильную ссылку.

1 голос
/ 15 марта 2013

Это очень полезно, так как вы можете использовать функцию непосредственно в качестве параметра 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];

}

Единственная проблема - безопасность потоков, но ее можно решить с помощью локальных переменных потоков (ключевое слово __thread gcc)

0 голосов
/ 17 января 2009

Да, это часто используется для возврата текстовой части какого-либо поиска, т. Е. Для перевода некоторого числа ошибки в удобную для человека строку.

Это целесообразно делать в тех случаях, когда вы:

fprintf(stderr, "Error was %s\n", my_string_to_error(error_code));

Если my_string_to_error() вернет выделенную строку, ваша программа будет иметь утечку, учитывая приведенное выше (очень) частое использование такой функции.

char const *foo_error(...)
{
    return "Mary Poppins";
}

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

Просто смотрите строки таким образом, не возвращайте книгу :)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...