Возврат локальных данных из функций в C и C ++ через указатель - PullRequest
6 голосов
/ 27 июня 2010

У меня есть спор с моим другом.Он говорит, что я могу вернуть указатель на локальные данные из функции.Это не то, что я узнал, но я не могу найти контраргумент для него, чтобы доказать мои знания.

Вот иллюстрированный случай:

char *name() {
    char n[10] = "bodacydo!";
    return n;
}

И он используется как:

int main() {
    char *n = name();
    printf("%s\n", n);
}

Он говорит, что это нормально, потому что после того, как программа вызывает имя, она возвращает указатель на n, а сразу после этого просто печатает его.Пока что в программе больше ничего не происходит, потому что она однопоточная, а выполнение - последовательное.

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

Другой пример:

int *number() {
    int n = 5;
    return &n;
}

int main() {
    int *a = number();
    int b = 9;
    int c = *a * b;
    printf("%d\n", c);
}

Я пришлю ему эту ссылкупосле того, как я получу несколько хороших ответов, он хотя бы чему-нибудь научится.

Ответы [ 13 ]

13 голосов
/ 27 июня 2010

Ваш друг не прав.

name возвращает указатель на стек вызовов. После вызова printf невозможно сказать, как этот стек будет перезаписан до получения доступа к данным в указателе. Может работать на его компиляторе и машине, но не на всех.

Ваш друг утверждает, что после возврата name "ничего не происходит, кроме его печати". printf сам по себе является еще одним вызовом функции, с которым известно, насколько сложна внутри него. Многое происходит до того, как данные напечатаны.

Кроме того, код никогда не завершается, он будет изменен и добавлен в. Код «теперь ничего не делает» будет делать что-то, как только оно будет изменено, и ваш аргументированный трюк развалится

Возвращение указателя на локальные данные - путь к катастрофе.

10 голосов
/ 27 июня 2010

Вы получите проблему, когда вызовете другую функцию между name () и printf (), которая сама использует стек

char *fun(char *what) {
   char res[10];
   strncpy(res, what, 9);
   return res;
}

main() {
  char *r1 = fun("bla");
  char *r2 = fun("blubber");
  printf("'%s' is bla and '%s' is blubber", r1, r2);
}
4 голосов
/ 27 июня 2010

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

Также более подробную информацию вы можете прочитать ЗДЕСЬ .

2 голосов
/ 27 июня 2010

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

Если произойдет вывод кода, правильное значение зависит от реализации printf() и от того, как вызовы функций работают на используемом вами компиляторе / платформе (какие параметры / адреса / переменные помещаются в стек, ... ). Даже если код «работает» на вашей машине с определенными настройками компилятора, далеко не обязательно, что он будет работать где-либо еще или в слегка отличающихся граничных условиях.

2 голосов
/ 27 июня 2010

Мои контраргументы будут такими:

  • Никогда не нормально писать код с неопределенным поведением ,
  • как долго, прежде чем кто-то еще использует эту функциюв другом контексте,
  • язык предоставляет возможность делать то же самое на законных основаниях (и, возможно, более эффективно)
1 голос
/ 28 июня 2010

Ты прав, твой друг не прав. Вот простой контрпример:

char *n = name();
printf("(%d): %s\n", 1, n);
1 голос
/ 27 июня 2010

gcc: main.c: в функции "name": main.c: 4: предупреждение: функция возвращает адрес локальной переменной

Где бы это ни было сделано (но это не сексуальный код: p):

char *name()
{
  static char n[10] = "bodacydo!";
  return n;
}

int main()
{
    char *n = name();

    printf("%s\n", n);
}

Внимание, это не потокобезопасно.

1 голос
/ 27 июня 2010

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

Вот контрпример, который дает сбой в моей системе, если он скомпилирован с включенной оптимизацией:

char * name ()
{
  char n[] = "Hello World";
  return n;
}

void test (char * arg)
{
  // msg and arg will reside roughly at the same memory location.
  // so changing msg will change arg as well:
  char msg[100];

  // this will override whatever arg points to.
  strcpy (msg, "Logging: ");

  // here we access the overridden data. A bad idea!
  strcat (msg, arg);

  strcat (msg, "\n");
  printf (msg);
}

int main ()
{
  char * n =  name();
  test (n);
  return 0;
}
1 голос
/ 27 июня 2010

Вы правы - n живет в стеке и поэтому может исчезнуть, как только функция вернется.

Код вашего друга может работать только потому, что область памяти, на которую указывает n, не была повреждена (все же!).

0 голосов
/ 13 января 2013

На мой взгляд, у вас есть три основных варианта, потому что этот опасен и использует неопределенное поведение:

заменить: char n[10] = "bodacydo!"

с: static char n[10] = "bodacydo!"

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

заменить:
char n[10] = "bodacydo!"

с:
char *n = new char[10]; *n = "bodacydo!"

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

Или, наконец:

заменить: char n[10] = "bodacydo!";

с: shared_ptr<char> n(new char[10]) = "bodacydo!";

Это избавляет вас от необходимости удалять память кучи, но затем вам придется изменить тип возвращаемого значения и char * n в main на shared_prt, чтобы передать управление указателем. Если вы не передадите его, область действия shared_ptr закончится, и значение, сохраненное в указателе, будет установлено в NULL.

...