вернуть указатель на данные, объявленные в функции - PullRequest
9 голосов
/ 23 февраля 2010

Я знаю, что это не сработает, потому что переменная x уничтожается, когда функция возвращает:

int* myFunction()
{
    int x = 4; return &x;
}

так как мне правильно вернуть указатель на что-то, что я создаю в функции, и с чем мне нужно позаботиться? Как избежать утечек памяти?

Я также использовал malloc:

int* myFunction2()
{
    int* x = (int*)malloc(sizeof int); *x = 4; return x;
}

Как вы правильно это делаете - в C и C ++?

Ответы [ 11 ]

7 голосов
/ 23 февраля 2010

Для C ++ вы можете использовать умный указатель для принудительной передачи права собственности. auto_ptr или boost::shared_ptr - хорошие варианты.

6 голосов
/ 23 февраля 2010

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

Из-за этой дополнительной сложности это редко делается для «маленьких» типов, таких как int, хотя я предполагаю, что вы просто использовали int здесь для примера.

Некоторые люди также предпочитают брать указатель на уже выделенный объект в качестве параметра, а не размещать объект внутри. Это проясняет, что вызывающая сторона ответственна за освобождение объекта (поскольку они выделяли его в первую очередь), но делает сайт вызова более многословным, поэтому это компромисс.

5 голосов
/ 23 февраля 2010

Для C ++, во многих случаях, просто вернуть по значению. Даже в случае более крупных объектов RVO часто позволяет избежать ненужного копирования.

3 голосов
/ 23 февраля 2010

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

void computeFoo(int *dest) {
    *dest = 4;
}

Это хорошо, потому что вы можете использовать такую ​​функцию с автоматической переменной:

int foo;
computeFoo(&foo);

При таком подходе вы также сохраняете управление памятью в той же части кода, т.е. вы не можете пропустить malloc только потому, что это происходит где-то внутри функции:

// Compare this:
int *foo = malloc(…);
computeFoo(foo);
free(foo);

// With the following:
int *foo = computeFoo();
free(foo);

Во втором случае легче забыть бесплатное, так как вы не видите malloc. Часто это по крайней мере частично решается соглашением, например: «Если имя функции начинается с XY, это означает, что вы владеете данными, которые она возвращает».

Интересный случай возврата указателя на переменную «function» - объявление статической переменной:

int* computeFoo() {
    static int foo = 4;
    return &foo;
}

Конечно, это плохо для нормального программирования, но когда-нибудь это может пригодиться.

2 голосов
/ 23 февраля 2010

C ++ подход, чтобы избежать утечек памяти. (по крайней мере, когда вы игнорируете вывод функции)

std::auto_ptr<int> myFunction() {
    std::auto_ptr<int> result(new int(4));
    return result;
}

Тогда назовите это:

std::auto_ptr<int> myFunctionResult = myFunction();

РЕДАКТИРОВАТЬ: Как указал Джоэл. std :: auto_ptr имеет свои недостатки и, как правило, его следует избегать. Вместо std :: auto_ptr Вы можете использовать boost :: shared_ptr (std :: tr1 :: shared_ptr).

boost::shared_ptr<int> myFunction() {
    boost::shared_ptr<int> result(new int(5));
    return result;
}

или при использовании компилятора, соответствующего C ++ 0x. Вы можете использовать std :: unique_ptr.

std::tr1::unique_ptr<int> myFunction() {
    std::tr1::unique_ptr<int> result(new int(5));
    return result;
}

Основное отличие состоит в том, что:

  • shared_ptr позволяет нескольким экземплярам shared_ptr указывать на один и тот же указатель RAW. Он использует механизм подсчета ссылок, чтобы гарантировать, что память не будет освобождена, пока существует хотя бы один экземпляр shared_ptr.

  • unique_ptr допускает только один его экземпляр с указателем, но имеет истинную семантику перемещения в отличие от auto_ptr.

1 голос
/ 23 февраля 2010

Ваш второй фрагмент кода правильный.

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

xxxCreate () выделит память для xxx и инициализирует ее. xxxDelete () уничтожит / испортит xxx и освободит его.

xxxInit () инициализирует xxx (никогда не выделяет) xxxDestroy () уничтожит / испортит xxx (никогда не освобождает)

Кроме того, я пытаюсь добавить код для удаления / уничтожения / освобождения, как только добавлю код для создания / init / malloc. Это не идеально, но я нахожу, что это помогает мне различать предметы, которые нужно освободить, и те, которые не освобождают, а также снижает вероятность того, что я забуду что-то освободить позже.

1 голос
/ 23 февраля 2010

Есть другой подход - объявить x статическим. В этом случае он будет расположен в сегменте данных, а не в стеке, поэтому он доступен (и постоянен) во время выполнения программы.

int *myFunction(void)
{
    static int x = 4;
    return &x;
}

Обратите внимание, что назначение x=4 будет выполняться только при первом вызове myFunction:

int *foo = myFunction();   // foo is 4
*foo = 10;                 // foo is 10
*foo = myFunction();       // foo is 10

NB! Использование статических переменных области действия не является безопасным для протектора способом.

1 голос
/ 23 февраля 2010

В C ++ вы должны использовать new:

int *myFunction()
{
    int blah = 4;
    return new int(blah);
}

И чтобы избавиться от него, используйте delete:

int main(void)
{
    int *myInt = myFunction();
    // do stuff
    delete myInt;
}

Обратите внимание, что я вызываю конструктор копирования для int при использовании new, поэтому значение "4" копируется в память кучи. Единственный способ надежно получить указатель на что-либо в стеке - это скопировать его в кучу, правильно вызвав new.

РЕДАКТИРОВАТЬ: Как отмечено в другом ответе, вам также нужно будет документально подтвердить, что указатель должен быть освобожден вызывающей стороной позже. В противном случае возможно утечка памяти.

0 голосов
/ 23 февраля 2010

Вы спрашиваете, как правильно вернуть указатель. Это неправильный вопрос, потому что вы должны использовать умные указатели, а не сырые указатели. scoped_ptr и shared_ptr (доступны в boost и tr1) - это хорошие указатели (например, здесь и здесь )

Если вам нужен необработанный указатель для чего-либо (например, передача функции C), метод get () предоставит его.

Если вы должны создавать необработанные указатели, например, для домашней работы вы можете использовать malloc () (как вы это сделали) или new внутри функции и надеяться, что вы не забудете освободить память (через free ( ) и delete соответственно) Или, в идиоме с чуть меньшей вероятностью утечки, вы можете создать указатель с помощью new , передать его функции и отмените выделение с помощью delete , когда закончите. Опять же, используйте умные указатели.

0 голосов
/ 23 февраля 2010

Я бы попробовал что-то вроде этого:

int myFunction2b( int * px )
{
  if( px )
  {
    *px = 4;
    return 1;
  }

  // Choice 1: Assert or Report Error
  // Choice 2: Allocate memory for x. Caller has to be written accordingly.

  // My choice is 1
  assert( 0 && "Argument is NULL pointer" );
  return 0;

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...