У меня есть спор с моим другом. Он говорит, что я могу возвратить указатель на локальные данные из функции. Это не то, что я выучил лишь, я не могу найти, что контрдовод для него доказывает мое знание.
Вот проиллюстрированный случай:
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);
}
Я отправлю ему эту ссылку после того, как я получу некоторые хорошие ответы, таким образом, он, по крайней мере, изучает что-то.
вы получите проблему, когда вызовете другую функцию между 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);
}
Если мы возьмем сегмент кода, который дал 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, скорее всего, не выйдет из строя, но, скорее всего, выйдет из строя, если деконструктор объектов выполняет некоторую деинициализацию ресурса, и мы будем использовать его впоследствии.
Надеюсь, это поможет
Верно, что вы не можете возвращать указатели на переменные локального стека, объявленные внутри функции, однако вы можете выделить память внутри функции, используя 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;
}
Возвращать указатель на локальную переменную всегда неправильно, даже если кажется, что в какой-то редкой ситуации он работает.
Локальная (автоматическая) переменная может быть выделена либо из стека, либо из регистров.
Даже если приложение является «однопоточным», прерывания могут использовать стек. Для обеспечения относительной безопасности следует отключить прерывания. Но отключить NMI (немаскируемое прерывание) невозможно, поэтому вы никогда не будете в безопасности.
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);
}
Предупреждение, это небезопасно для потоков.
Вы правы, ваш друг ошибается. Вот простой контрпример:
char *n = name();
printf("(%d): %s\n", 1, n);
Не определено поведение, и значение может быть легко уничтожено до того, как оно будет напечатано. printf ()
, которая является обычной функцией, может использовать некоторые локальные переменные или вызывать другие функции до того, как строка будет фактически напечатана. Поскольку эти действия используют стек, они могут легко повредить значение.
Если код печатает правильное значение, это зависит от реализации printf ()
и того, как вызовы функций работают на используемом компиляторе / платформе (какие параметры / адреса / переменные помещаются в стек, ...). Даже если код «работает» на вашем компьютере с определенными настройками компилятора, нельзя быть уверенным, что он будет работать где-нибудь еще или при немного иных граничных условиях.
Вы правы - n живет в стеке и поэтому может исчезнуть, как только функция вернется.
Код вашего друга может работать только потому, что область памяти, на которую указывает n, не была повреждена (пока!).
Мои контраргументы:
Как только область действия функции заканчивается, т.е. после закрывающей скобки} функции, память, выделенная (в стеке) для всех локальных переменных, будет оставлена. Итак, возвращение указателя на некоторую память, которая больше не действительна, вызывает неопределенное поведение. Также вы можете сказать, что время жизни локальной переменной заканчивается, когда функция завершает выполнение.
Также более подробно вы можете прочитать ЗДЕСЬ .
Ваш друг ошибается.
name
возвращает указатель на стек вызовов. Когда вы вызовете printf
, неизвестно, как этот стек будет перезаписан до того, как данные по указателю будут доступны. Это может сработать на его компиляторе и машине, но не на всех.
Ваш друг утверждает, что после возврата name
"ничего не происходит, кроме печати". printf
- это еще один вызов функции, внутри которой неизвестно сколько сложностей. Многое происходит до того, как данные будут напечатаны.
Кроме того, код никогда не бывает законченным, он будет изменяться и дополняться. Код, который "ничего не делает" сейчас, будет делать что-то, когда его изменят, и ваш тщательно продуманный трюк развалится.
Возврат указателя на локальные данные - это рецепт катастрофы.
Как уже указывали другие, это не незаконно, но плохая идея, потому что возвращенные данные находятся в неиспользуемой части стека и могут быть переопределены в любое время другими вызовами функций.
Вот контрпример, который аварийно завершает работу моей системы, если скомпилирован с включенной оптимизацией:
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;
}