通过指针从C和C ++中的函数返回本地数据

我跟朋友争吵了。 他说我可以从函数返回指向本地数据的指针。 这不是我所学到的,但我找不到反对他来certificate我的知识的反驳。

这是一个例子:

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

它用作:

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

他说这是完全可以的,因为在一个程序调用name后,它会返回一个指向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); } 

你的朋友错了。

name返回一个指向调用堆栈的指针。 一旦调用printf ,就无法知道在访问指针的数据之前该堆栈是如何被覆盖的。 它可能适用于他的编译器和机器,但它不适用于所有这些。

你的朋友声称,在name返回后,“除了打印它之外什么也没发生”。 printf本身就是另一个函数调用,谁知道它内部有多少复杂性。 在打印数据之前会发生很多事情。

此外,代码永远不会完成,它将被修改并添加到。 编码“什么都不做”现在一旦发生变化就会做一些事情,而你理所当然的伎俩就会崩溃。

返回指向本地数据的指针是一种灾难。

一旦函数的范围结束,即在函数的闭括号之后,将保留为所有局部变量分配(在堆栈上)的内存。 因此,返回指向某个不再有效的内存的指针会调用未定义的行为。 此外,您可以说当函数完成执行时,局部变量生命周期结束。

您还可以在此处阅读更多详细信息。

我的反驳论据是:

  • 编写具有未定义行为的代码永远不行,
  • 在其他人在不同的环境中使用该function之前多久,
  • 该语言提供了合法地做同样事情的设施(并且可能更有效)

它是未定义的行为,在实际打印之前,该值很容易被破坏。 printf() ,它只是一个普通函数,可以在实际打印字符串之前使用一些局部变量或调用其他函数。 由于这些操作使用堆栈,因此很容易破坏价值。

如果代码碰巧打印正确的值取决于printf()的实现以及函数调用如何在您正在使用的编译器/平台上工作(哪些参数/地址/变量放在堆栈的哪里,…)。 即使代码恰好在你的机器上使用某些编译器设置“工作”,也很难确定它是否可以在其他任何地方或稍微不同的边界条件下工作。

你是正确的 – n生活在堆栈上,因此一旦函数返回就会消失。

你朋友的代码可能只是因为n指向的内存位置没有被破坏(但是!)。

正如其他人已经指出的那样,这样做并不违法,但这是一个坏主意,因为返回的数据驻留在堆栈的未使用部分,并且可能随时被其他函数调用覆盖。

这是一个反例,如果在打开优化的情况下进行编译,则会在我的系统上崩溃:

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

你是对的,你的朋友错了。 这是一个简单的反例:

 char *n = name(); printf("(%d): %s\n", 1, n); 

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

警告它不是线程安全的。

返回指向局部变量的指针是错误的,即使它似乎在一些罕见的情况下工作。

可以从堆栈或寄存器分配本地(自动)变量。

  • 如果它是从堆栈分配的,则只要执行下一个函数调用(例如printf)或发生中断,它就会被覆盖。
  • 如果变量是从寄存器中分配的,则甚至不可能有指向它的指针。

即使应用程序是“单线程”, 中断也可以使用堆栈。 为了相对安全,您应该禁用中断。 但是无法禁用NMI(不可屏蔽中断),因此您永远不会安全。

虽然您无法返回指向函数内声明的本地堆栈变量的指针,但您可以使用malloc在函数内部分配内存,然后返回指向该块的指针。 也许这就是你朋友的意思?

 #include #include #include 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; } 

我看到它的方式有三个主要选项,因为这个选项很危险并且使用了未定义的行为:

替换: char n[10] = "bodacydo!"

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

如果您在尝试维护其中包含的值时多次使用相同的函数,则会产生不良结果。

更换:
char n[10] = "bodacydo!"

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

将修复上述问题,但您将需要删除堆内存或开始导致内存泄漏。

或者最后:

replace: char n[10] = "bodacydo!";

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

这使您不必删除堆内存,但是您将在main中将返回类型和char * n更改为shared_prt,以便切换指针的管理。 如果不将其关闭,shared_ptr的范围将结束,并且存储在指针中的值将设置为NULL。

如果我们采取你给的代码段….

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

可以在main’中使用printf()中的局部变量’这里我们使用的是字符串文字,这也不是name()本地的东西。

但现在让我们看一下略有不同的代码

 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。

所以当我们在main中调用print()时,即使由于n指向的mem没有被覆盖(我假设),当它试图取消引用NULL指针时,print调用将崩溃。

因此,在某种程度上,如果对象解构器正在执行某些资源取消初始化并且之后我们正在使用它,那么您的代码段很可能不会失败但很可能会失败。

希望能帮助到你