Возврат локальных данных из функций в C и C++ через указатель

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

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

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

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

6
задан Brian Tompsett - 汤莱恩 6 November 2015 в 12:45
поделиться

12 ответов

вы получите проблему, когда вызовете другую функцию между 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);
}
10
ответ дан 8 December 2019 в 02:45
поделиться

Если мы возьмем сегмент кода, который дал u ....

char *name() {
    char n[10] = "bodacydo!";
    return n;
}
int main() {
    char *n = name();
    printf("%s\n", n);
}

Это нормально использовать эту локальную переменную в printf () в main ', потому что здесь мы используем строковый литерал, который снова не является не называйте что-то локальное ().

Но теперь давайте посмотрим на немного другой код.

class SomeClass {
    int *i;
public:
    SomeClass() {
        i = new int();
        *i = 23;
    }
    ~SomeClass() {
        delete i;
        i = NULL;
    }
    void print() {
        printf("%d", *i);
    }
};
SomeClass *name() {
    SomeClass s;
    return &s;
}
int main() {
    SomeClass *n = name();
    n->print();
}

В этом случае, когда функция name () возвращает функцию SomeClass, будет вызван деструктор, а член var i будет освобожден и установлен в NULL.

Итак, когда мы вызываем print () в main, даже если память, на которую указывает n, не перезаписывается (я предполагаю, что), вызов print завершится аварийно, когда он попытается отменить ссылку на NULL-указатель.

Таким образом, в некотором смысле сегмент кода ur, скорее всего, не выйдет из строя, но, скорее всего, выйдет из строя, если деконструктор объектов выполняет некоторую деинициализацию ресурса, и мы будем использовать его впоследствии.

Надеюсь, это поможет

-2
ответ дан 8 December 2019 в 02:45
поделиться

Верно, что вы не можете возвращать указатели на переменные локального стека, объявленные внутри функции, однако вы можете выделить память внутри функции, используя malloc , а затем вернуть указатель на этот блок. Может быть, это то, что имел в виду ваш друг?

#include<stdio.h>
#include<string.h>
#include<stdlib.h> 

char* getstr(){
    char* ret=malloc(sizeof(char)*15);
    strcpy(ret,"Hello World");
    return ret;
}
int main(){
    char* answer=getstr();
    printf("%s\n", answer);
    free(answer);
    return 0;
}
0
ответ дан 8 December 2019 в 02:45
поделиться

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

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

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

Даже если приложение является «однопоточным», прерывания могут использовать стек. Для обеспечения относительной безопасности следует отключить прерывания. Но отключить NMI (немаскируемое прерывание) невозможно, поэтому вы никогда не будете в безопасности.

0
ответ дан 8 December 2019 в 02:45
поделиться

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

Везде, где это можно было сделать так (но это не сексуальный код: p):

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

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

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

Предупреждение, это небезопасно для потоков.

1
ответ дан 8 December 2019 в 02:45
поделиться

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

char *n = name();
printf("(%d): %s\n", 1, n);
1
ответ дан 8 December 2019 в 02:45
поделиться

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

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

2
ответ дан 8 December 2019 в 02:45
поделиться

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

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

1
ответ дан 8 December 2019 в 02:45
поделиться

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

  • никогда не хорошо писать код с неопределенным поведением,
  • как скоро кто-то другой использует эту функцию в другом контексте,
  • язык предоставляет средства, чтобы сделать то же самое легально (и, возможно, более эффективно)
2
ответ дан 8 December 2019 в 02:45
поделиться

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

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

4
ответ дан 8 December 2019 в 02:45
поделиться

Ваш друг ошибается.

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

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

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

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

13
ответ дан 8 December 2019 в 02:45
поделиться

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

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

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
ответ дан 8 December 2019 в 02:45
поделиться
Другие вопросы по тегам:

Похожие вопросы: