4

我正在查看朋友的代码,并就 C/C++ 如何在堆栈上分配内存并管理其释放进行了有趣的辩论。如果我要在一个函数中创建一个包含 10 个对象的数组,但返回该数组,它是在函数弹出时释放(因此使给定数据无效)还是放入堆中(这引发了我们如何释放它?)。

示例代码如下:

Gene* GetTopTen()
{
    // Create 10 genes (or 10 objects, doesn't matter)
    Gene Ten[10];

    // Sort out external pool data
    Sort();

    // Copy over data to the array of 10 objects
    for(int i = 0; i < 10; Ten[i++] = pool[i]);

    // Here is the core of my question:
    return Ten;
}

非常感谢任何帮助,这变成了一个非常有趣的问题,我的朋友们,我无法回答。

4

8 回答 8

22

那是一个堆栈分配的数组,所以返回的指针是无效的。

于 2009-04-29T23:03:23.783 回答
9

一旦函数返回,数据不会被破坏,但可能会被下一个函数调用覆盖。您可能会很幸运,它可能仍然存在,但这是未定义的行为。你不应该依赖它。

以下是可能发生的情况:在函数执行期间,堆栈如下所示:

"---------------------------
| caller function's data   |
----------------------------
| Ten[9]                   |
| Ten[8]                   |
| ...                      |
| Ten[0]                   |
---------------------------"

函数退出后,它可能立即看起来相同。但是如果调用者像这样调用另一个函数,

void some_func() {
    Gene g;
    ...
}

堆栈现在看起来像这样:

"---------------------------
| caller function's data   |
----------------------------
| g                        |
----------------------------
| Ten[8]                   |
| ...                      |
| Ten[0]                   |
---------------------------"

某些数据可能会被静默覆盖(​​在这种情况下是Ten[9]),而您的代码将不知道它。您应该使用 分配堆上的数据malloc()并使用 显式释放它free()

于 2009-04-29T23:07:34.320 回答
3

这会导致未定义的行为。十个数组存储在堆栈中。一旦 GetTopGene 函数结束,该堆栈就会被销毁,因此您不应该使用指向该部分内存的指针。

您要做的是在堆中分配空间。

Gene* GetTopTen(){
        Gene* genes = (Gene*) malloc (10*sizeof(Gene));
        //do stuff.
         return genes;
}

完成后,您必须记住释放该内存。

   Gene* genes = GetTopTen();
   //do stuff with it.
   free(genes);
于 2009-04-29T23:05:19.923 回答
2

另一种无需分配堆内存而正确执行此操作的方法是更改​​函数的原型以获取指向数组的指针以将结果写入:

无效GetTopTen(基因十[]){...}

然后只需删除函数体中 Ten 的声明,因为它现在是一个参数。

调用者现在需要声明自己的十元素数组并将其作为参数传递:

... 基因顶部[10]; GetTopTen(顶部); ...

请注意调用者声明一个足够大的数组!不幸的是,C 没有一个好的方法让函数指定应该传入的数组的大小,所以如果调用者声明的数组太小,编译器不会警告你;它只会在运行时覆盖堆栈。

于 2009-04-29T23:29:25.937 回答
2

这是最可怕的错误的原因!有时它会起作用,有时它不会。

这是一个错误,而不是“聪明的代码”。

于 2009-04-29T23:17:15.747 回答
1

如果你想从一个函数返回一个数组,你必须自己把它放在堆上:

在 C 中

Gene* GetTopTen()
{
    // Create 10 genes (or 10 objects, doesn't matter)
    Gene *Ten = malloc(sizeof(Gene)*10);
    ....    
    // Now it's ok to return
    return Ten;
}

int main()
{
    Gene *genes = GetTopTen();
    free (genes);
}

在 C++ 中:

Gene* GetTopTen()
{
    // Create 10 genes (or 10 objects, doesn't matter)
    Gene *Ten = new Gene[10];
    ....    
    // Now it's ok to return
    return Ten;
}

int main()
{
    Gene *genes = GetTopTen();
    delete [] genes;
}

当然,您也可以通过在指针/引用参数中从 GetTopTen 返回一个长度,或者通过某种常量来跟踪该数组的长度。

于 2009-04-29T23:06:45.600 回答
0

你的函数说它返回一个指针,所以当你的函数被调用时,只有指针的空间会在堆栈上分配给返回值。因此,您的返回值是一个指针,它指向堆栈上的某个位置,当您的函数退出时该位置将无效。

将您的返回值包装在一个struct.

typedef struct
{
    int Array[10];
} ArrayOfTen;

ArrayOfTen SomeFunction ()
{
    ArrayOfTen array = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}};
    return array;
}

int main (int argc, char *argv[])
{
    ArrayOfTen array = SomeFunction ();
    int i;

    for (i = 0; i < 0; i++)
        fprintf (stdout, "%d\n", array.Array[i]);

    return 0;
}
于 2009-04-29T23:08:14.503 回答
0

这不是错误,这只是一种低级语言。您(几乎)不能堆栈分配数组并在同一个函数中返回它 - 直到您不是(不)幸运的人之一。而且,你不应该在任何情况下都调用 malloc。

为了使您的程序对内存友好,请在调用者的堆栈中分配此数组并将其作为参数传递。

GetTopTen 函数将如下所示:

void GetTopTen(Gene* genes) {
   /*
    Do something
    Do not allocate new Gene array, you already have one - modify values at "genes" pointer
    */
}

叫它:

Gene genes[10];
GetTopTen(genes);
/*
 Now, do whatever you want with top ten "genes", which are safe until return from this function
 */

正如我现在看到的那样,这种风格没有被使用,很遗憾我不是在开玩笑,因为这是低级语言与垃圾收集的不同malloc()之处,而且比一直调用要快得多;这些变量会自动释放并占用更少的内存。但请注意,它取决于结构的大小和元素的数量 - 如果它真的很大,请通过 malloc 分配它,否则可能会导致堆栈溢出。

于 2015-07-11T17:44:19.260 回答