2010-02-23 21 views
9

Sé que esto no va a funcionar porque la variable x se destruye cuando se devuelve la función:retorno puntero a los datos declarados en la función

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

Entonces, ¿cómo correctamente devuelven un puntero a algo que se crea dentro de la función y ¿de qué tengo que ocuparme? ¿Cómo evito las pérdidas de memoria? malloc

También he utilizado:

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

¿Cómo hacer esto correctamente - en C y C++?

+3

Tiene razón, la respuesta es 'myFunction2()' solo tiene que recordar liberar su memoria más tarde. Ese es el problema de no tener un recolector de basura – tzenes

+4

Una vez más, mientras que C y C++ comparten muchas características, hay muchas preguntas con respuestas completamente diferentes según el idioma. Cómo evitar fugas de memoria es una de ellas, ya que es cómo crear algo dentro de una función ... ¿En qué idioma estás realmente interesado? –

Respuesta

6

Su segundo enfoque es el correcto. Solo necesita documentar claramente que la persona que llama "posee" el puntero de resultado y es responsable de liberarlo.

Debido a esta complejidad adicional, es raro hacer esto para tipos "pequeños" como int, aunque asumo que acaba de utilizar una int aquí por el simple ejemplo.

Algunas personas también preferirán tomar un puntero a un objeto ya asignado como parámetro, en lugar de asignar el objeto internamente. Esto deja en claro que la persona que llama es responsable de la desasignación del objeto (ya que lo asignaron en primer lugar), pero hace que el sitio de la llamada sea un poco más detallado, por lo que es una solución de compromiso.

5

Para C++, en muchos casos, simplemente devuelva por valor. Incluso en casos de objetos más grandes, RVO evitará con frecuencia la copia innecesaria.

1

En C++, debe utilizar new:

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

Y para deshacerse de él, utilice Delete:

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

Nota que estoy invocando el constructor de copia para int durante el uso de new , de modo que el valor "4" se copie en la memoria del montón. La única forma de obtener un puntero a algo en la pila confiablemente es copiarlo en el montón invocando new correctamente.

EDITAR: Como se señala en otra respuesta, también deberá documentar que el puntero debe ser liberado por la persona que llama más adelante. De lo contrario, es posible que tenga una pérdida de memoria.

2

C++ enfoque para evitar fugas de memoria. (Al menos cuando se ignora la función de salida)

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

Entonces llaman:

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

EDIT: Como se ha señalado por Joel. std :: auto_ptr tiene sus propios inconvenientes y generalmente debe evitarse. En cambio std :: auto_ptr Puede usar boost :: shared_ptr (std :: tr1 :: shared_ptr).

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

o cuando se usa C++ 0x compilador conforme Se puede usar std :: unique_ptr.

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

La diferencia principal es que:

  • shared_ptr permite varias instancias de señalador shared_ptr al mismo puntero RAW. Utiliza un mecanismo de recuento de referencias para garantizar que la memoria no se libere siempre que exista al menos una instancia de shared_ptr.

  • unique_ptr permite que solo una instancia de la misma sostenga el puntero pero tenga la semántica del movimiento verdadero a diferencia de auto_ptr.

+0

auto_ptr generalmente no está bien visto porque tiene una semántica extraña. Cuando pasas ese resultado a una función, ya no eres dueño de ella y ya no puedes acceder a ella. – Joel

+0

Tienes razón y estoy completamente de acuerdo. Es por eso que escribí (al menos cuando ignoras el resultado de la función). La mejor solución es, por ejemplo, boost :: shared_ptr, pero sugerí una solución estandarizada actualmente. Y sí, sé sobre std :: tr1 :: shared_ptr – lollinus

+0

@Joel: si pasa a otra función, esa función probablemente no tomará un auto_ptr. Entonces usarías 'f (myFunctionResult.get())' y aún conservaría la propiedad. Si la función toma un auto_ptr, está claro que se transfiere la propiedad. –

7

Para C++, puede usar un puntero inteligente para imponer la transferencia de propiedad. auto_ptr o boost::shared_ptr son buenas opciones.

3

Una posibilidad está pasando a la función de un puntero:

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

Esto es bueno porque se puede utilizar esta función con una variable automática:

int foo; 
computeFoo(&foo); 

Con este enfoque también se guarda la memoria gestión en la misma parte del código, es decir. no se puede perder un malloc simplemente porque ocurre en algún lugar dentro de una función:

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

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

En el segundo caso es más fácil de olvidar la libre, ya que no ve el malloc. Esto a menudo se resuelve al menos parcialmente por convención, por ejemplo: "Si el nombre de una función comienza con XY, significa que usted es el propietario de los datos que devuelve".

Se está declarando un caso de esquina interesante para regresar a la variable "función" la variable estática:

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

por supuesto, esto es malo para la programación normal, pero podría venir a mano algún día.

+0

estático es bueno; es triste no es = malloc – xealits

1

Hay otro enfoque: declarar x estático. En este caso, se ubicará en el segmento de datos, no en la pila, por lo tanto, estará disponible (y persistirá) durante el tiempo de ejecución del programa.

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

Tenga en cuenta que la asignación x=4 se llevará a cabo sólo en la primera llamada de myFunction:

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

NB! El uso de variables estáticas de ámbito de función no es una técnica segura para la banda de rodadura.

+0

¿Por qué el voto a favor? Mi respuesta es completamente correcta. – qrdl

0

Boost o punteros compartidos TR1 son generalmente el camino a seguir. Evita la sobrecarga de la copia y le proporciona una eliminación semiautomática. Entonces su función debería verse así:

boost::shared_ptr<int> myFunction2() 
{ 
    boost::shared_ptr<int> x = new int; 

    *x = 4; 
    return x; 
} 

La otra opción es simplemente permitir una copia. Eso no es tan malo si el objeto es pequeño (como este) o puede organizar para crear el objeto en la declaración de devolución. El compilador normalmente optimizará una copia si el objeto se crea en la declaración de devolución.

0

me gustaría probar algo como esto:

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; 

} 
-1

Estás preguntando cómo devolver correctamente un puntero. Esa es la pregunta incorrecta, porque lo que debería hacer es usar punteros inteligentes en lugar de punteros crudos. scoped_ptr y shared_ptr (disponible en alza y TR1) son buenos indicadores de ver (por ejemplo here y here)

Si necesita el puntero prima para algo (por ejemplo, pasar a una función C), el get() método lo suministrará.

Si debe crear punteros crudos, p. para hacer la tarea, entonces se puede utilizar malloc() (como lo hizo) o nueva dentro de una función, y esperamos que recuerda a desasignar la memoria (a través de libre() y eliminar respectivamente) O, en una expresión idiomática levemente menos probable, puede crear el puntero con nuevo, pasarlo a una función y desasignar con eliminar cuando haya terminado con él. De nuevo, sin embargo, use punteros inteligentes.

1

Su segundo fragmento de código es correcto.

Para ayudar a evitar fugas de memoria, dejo que las convenciones de codificación me ayuden.

xxxCreate() asignará memoria para xxx e inicializará. xxxDelete() destruirá/dañará xxx y lo liberará.

xxxInit() inicializará xxx (nunca asignar) xxxDestroy() va a destruir/xxx corrupto (no libre)

Además, trato de añadir el código para eliminar/destruir/libre tan pronto como añado el código para crear/init/malloc. No es perfecto, pero considero que me ayuda a diferenciar entre los artículos que necesitan ser liberados y los que no, así como también reducir la probabilidad de que me olvide de liberar algo en otro momento.

Cuestiones relacionadas