Возвращение данных стека в C; Правильно ли он распределяется? - PullRequest
4 голосов
/ 30 апреля 2009

Я просматривал код друга и вступил в интересную дискуссию о том, как 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;
}

Любая помощь очень ценится, это превращается в очень интересный вопрос, друзья мои, и я не могу ответить.

Ответы [ 8 ]

22 голосов
/ 30 апреля 2009

Это массив, выделенный стеком, поэтому возвращаемый указатель недействителен.

9 голосов
/ 30 апреля 2009

Как только функция вернется, данные не будут уничтожены, но, вероятно, будут перезаписаны при следующем вызове функции. Вам может повезти, и он все еще может быть, но это неопределенное поведение. Вы не должны на это полагаться.

Вот что может произойти: во время функции стек выглядит так:

"---------------------------
| 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().

3 голосов
/ 30 апреля 2009

В результате неопределенное поведение . Десять массив хранится в стеке. После завершения функции GetTopGene этот стек уничтожается, поэтому вы не должны использовать указатели на эту часть памяти.

То, что вы хотите сделать, это выделить место в куче.

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

Вы должны помнить, чтобы освободить эту память, как только закончите.

   Gene* genes = GetTopTen();
   //do stuff with it.
   free(genes);
2 голосов
/ 30 апреля 2009

Другой способ сделать это правильно, без необходимости выделять кучу памяти, это изменить прототип функции, чтобы взять указатель на массив для записи результата в:

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

затем просто удалите объявление Ten в теле функции, поскольку теперь это параметр.

Теперь вызывающей стороне необходимо объявить собственный массив из десяти элементов и передать его в качестве параметра:

... Ген топ [10]; GetTopTen (сверху); ...

Только будьте осторожны, чтобы вызывающая сторона объявила достаточно большой массив! К сожалению, C не имеет хорошего способа указать размер массива, который должен быть передан, поэтому компилятор не предупредит вас, если вызывающая сторона объявляет слишком маленький массив; он просто перезапишет стек во время выполнения.

2 голосов
/ 30 апреля 2009

Это причина самых отвратительных ошибок! Иногда это будет работать, а иногда нет.

Это ошибка, а не "умный кусок кода".

1 голос
/ 30 апреля 2009

Если вы хотите вернуть массив из функции, вам придется поместить его в кучу самостоятельно:

В С

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

А на С ++:

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 в параметре указатель / ссылка, либо по некоторой константе.

0 голосов
/ 11 июля 2015

Это не ошибка, это просто язык низкого уровня. Вы (почти) не можете разместить массив в стеке и вернуть его в той же функции - пока вы не один из (не) счастливчиков. Но также вы не должны вызывать 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, иначе вы можете получить переполнение стека.

0 голосов
/ 30 апреля 2009

Ваша функция сообщает, что возвращает указатель, поэтому, когда ваша функция вызывается, в стеке для возвращаемого значения выделяется только место для указателя. Следовательно, возвращаемое значение является указателем, указывающим на место в стеке, которое будет недействительным при выходе из вашей функции.

Оберните ваше возвращаемое значение в 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;
}
...