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

その結果、未定義の動作が発生します。10 個の配列がスタックに格納されます。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

これが最も恐ろしいバグの原因です!うまくいくこともあれば、うまくいかないこともあります。

これはバグであり、「巧妙なコード」ではありません。

于 2009-04-29T23:17:15.747 に答える
2

ヒープメモリを割り当てずにこれを正しく行う別の方法は、関数のプロトタイプを変更して、結果を書き込む配列へのポインターを取得することです。

void GetTopTen( Gene Ten[] ) { ... }

次に、関数本体で Ten の宣言を削除するだけです。これはパラメーターになっているためです。

呼び出し元は、独自の 10 要素の配列を宣言し、それをパラメーターとして渡す必要があります。

...遺伝子トップ[10]; GetTopTen(トップ); ...

呼び出し元が十分に大きな配列を宣言していることに注意してください! 残念ながら、C には、関数が渡される配列のサイズを指定する適切な方法がないため、呼び出し元が小さ​​すぎる配列を宣言しても、コンパイラは警告しません。実行時にスタックを上書きするだけです。

于 2009-04-29T23:29:25.937 に答える
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

これはバグではなく、単なる低水準言語です。あなたは(ほとんど)配列をスタック割り当てして同じ関数で返すことはできません-あなたが(不)幸運な人の一人でなくなるまで。しかし、すべての状況で 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 に答える
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 に答える