【问题标题】:Returning local data from functions in C and C++ via pointer通过指针从 C 和 C++ 中的函数返回本地数据
【发布时间】:2011-03-08 20:04:54
【问题描述】:

我和朋友吵架了。他说我可以从函数返回指向本地数据的指针。这不是我学到的,但我找不到他的反驳来证明我的知识。

以下是图解案例:

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

等我得到一些好的答案后,我会把这个链接发给他,这样他至少会学到一些东西。

【问题讨论】:

  • 你的团队有多少人?你有没有想过将“代码审查”作为“改进”(金钱、职位、声望)的基础 Codeview 不是“过度控制”,而是一种软件质量的方式。
  • 我们不在一个团队中,他只是我的朋友,也写 C 和 C++。我其实是个学生。

标签: c memory return local


【解决方案1】:

你的朋友错了。

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

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

此外,代码永远不会完成,它会被修改和添加。现在,“什么都不做”的代码一旦更改就会有所作为,而您的合理诡计将分崩离析。

返回指向本地数据的指针是灾难的根源。

【讨论】:

    【解决方案2】:

    当你在 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);
    }
    

    【讨论】:

    • 我手头没有编译器,所以我不知道这会产生什么结果,但我希望它是 'blubber' 是 bla 而 'blubber' 是 blubber。
    • 还值得注意:在一台机器上我得到'blubber' is bla and 'blubber' is blubber 在我的mac上我得到'' is bla and '' is blubber
    • Peter:当您手头没有编译器时,请尝试使用在线编译器,例如 s ideone.com(它会提供您预期的结果)。
    • malloc()free() 或者一个额外的参数(最好是第一个),所以char r1[10]; fun(r1, "bla");
    【解决方案3】:

    一旦函数的作用域结束,即在函数的右大括号 } 之后,所有局部变量的内存分配(在堆栈上)将被留下。因此,返回指向某个不再有效的内存的指针会调用未定义的行为。 也可以说局部变量的生命周期在函数执行完毕时结束。

    还有更多细节你可以阅读HERE

    【讨论】:

    • 这不太对:有问题的内存在堆栈上,所以它没有被释放,它保持原样,但它会在后续的函数调用中被覆盖。跨度>
    • 我说它是由编译器发布的,意思是编译器不会为了这个目的而保留它。无论如何,让我更新它。
    【解决方案4】:

    我的反驳是:

    • 未定义的行为编写代码是绝对不行的
    • 其他人在不同情况下使用该函数需要多长时间,
    • 该语言为合法地做同样的事情提供了便利(并且可能更有效)

    【讨论】:

    • 这些论点不适合固执的人!!!!!!他只会说“它适用于我的编译器,我不在乎(或知道)ub”并继续编写他的代码。只要代码有效,他就按照自己的方式编写。 :( 如果我能展示一个崩溃的例子,那也许会教他。
    • 这是我尝试构建的一个很好的例子。安排堆栈有指向puts() 函数和字符串“format /y c:”的指针,然后调用一个函数,该函数使用函数指针和char 指针作为其参数。现在,找到一种方法来“破坏”(我松散地使用这个词,因为无论如何内容都是未定义的)之间的堆栈,以便将指向 puts() 的指针替换为 system() 的地址,并将它交给你的朋友. :-)
    • R.. - 这可能是最好的例子。我要稍微混淆一下,然后给他。这会给他很好的教训。
    • 如果你想做个好人(和懒惰)你可以跳过弄清楚如何获取系统地址来覆盖函数指针,只需给他代码并让他运行它。如果他在看到“format /y c:”时退缩了,你就说明了你的意思。 :-)
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      你是对的 - n 存在于堆栈中,因此一旦函数返回就可能消失。

      您朋友的代码可能只是因为 n 指向的内存位置尚未损坏(还没有!)。

      【讨论】:

      • 他一定很幸运。我把整个线程都发给他。彼得的例子完美表明它不起作用。
      【解决方案7】:

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

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

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

      【讨论】:

        【解决方案8】:

        gcc : main.c: 在函数“名称”中: main.c:4: 警告:函数返回局部变量的地址

        无论在哪里都可以这样做(但它不是性感的代码:p):

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

        警告它不是线程安全的。

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            【解决方案10】:

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

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

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

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

            【讨论】:

              【解决方案11】:

              虽然您确实不能返回指向函数内部声明的局部堆栈变量的指针,但是您可以使用 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;
              }
              

              【讨论】:

              • 不,他的意思正是我所说的。您粘贴的内容是每个程序员都应该做的。 :) 他是个白痴,不知道这一点。
              【解决方案12】:

              在我看来,您有三个主要选择,因为这个很危险并且利用了未定义的行为:

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

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

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

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

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

              With 将解决上述问题,但您需要删除堆内存或开始引发内存泄漏。

              或者最后:

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

              与:shared_ptr&amp;lt;char&amp;gt; n(new char[10]) = "bodacydo!";

              这使您不必删除堆内存,但是您必须将返回类型和 main 中的 char *n 更改为 shared_prt ,以便移交指针的管理。如果你不交出它,shared_ptr 的作用域将会结束并且存储在指针中的值被设置为 NULL。

              【讨论】:

                【解决方案13】:

                如果我们拿你给的代码段....

                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 指向的内存没有被覆盖(我假设),当它试图取消引用 NULL 指针时,print 调用也会崩溃。

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

                希望对你有帮助

                【讨论】:

                • 不行,第一个例子绝对不行。字符串字面量用于初始化数组,与char *n = "bodacydo!";不同。
                猜你喜欢
                • 2010-12-25
                • 1970-01-01
                • 1970-01-01
                • 2018-09-13
                • 1970-01-01
                • 2014-03-30
                • 2015-08-05
                • 2018-09-13
                • 2012-02-26
                相关资源
                最近更新 更多