Foro de elhacker.net

Programación => Programación C/C++ => Mensaje iniciado por: digimikeh en 10 Febrero 2019, 22:43 pm



Título: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 10 Febrero 2019, 22:43 pm
Hola!
Me he topado con el siguiente escenario:

Código
  1.  
  2. //En este fragmento al terminar la función, se destruye el contenido de suma y retorna null o vacío, y el puntero suma sigue existiendo en memoria....
  3. int RetornaUnaSuma(int a, int b){
  4.  
  5.     int * suma = new int;
  6.     *suma = a+b;
  7.     return *suma;
  8. }
  9.  
  10. //Aqui intento retornar un puntero creado dentro de la misma función...
  11. int * RetornaUnaSuma(int a, int b){
  12.  
  13.     int * suma = new int;
  14.     *suma = a+b;
  15.     return suma;
  16.  
  17.     //pero al terminar la función, se destruye el contenido y retorna una dirección de memoria sin contenido, el puntero sigue existiendo..
  18. }
  19.  
  20.  

Según yo es mala práctica declarar punteros que quieras retomar dentro de una función o un ámbito...  bueno aparte de mala practica no funciona el retorno...Es como lo pienso?...

Probablemente se pueda devolver punteros cuando éstos mismos ingresan como argumento...

La función sería:

Código
  1. //Segun yo esto debe funcionar...
  2. int * RetornaUnaSuma(int * pSuma, int a, int b){
  3.  
  4.     *pSuma = a+b;
  5.     return pSuma;
  6.  
  7. }
  8.  


Que opinan...
Saludos.


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: Loretz en 10 Febrero 2019, 23:16 pm
Cuando pasas un puntero no constante como parámetro de una función, se sobreentiende que la intención es cambiar el valor de lo apuntado, igual que con las referencias.

En tu caso, la función podría ser:
Código:
void RetornaUnaSuma(int* pSuma, int a, int b)
{
    *pSuma = a + b;
}

Donde pSuma se pasa como puntero no constante, y cualquiera que ve su declaración (en un .h por ejemplo), asume que la idea de la función es modificar eso a lo que apunta.

Su uso sería:
Código:
int main() 
{
    int a = 1;
    int b = 2;
    int* pSuma = new int;

    RetornaUnaSuma(pSuma, a, b);

    std::cout << "*pSuma == " << *pSuma << '\n';

    // y por supuesto:
    delete pSuma;

}

Como ves, que se devuelva un puntero a int no está mal, sólo que no es necesario.

Aunque no puedo dejar de mencionar que en C++ no se usa new / delete, casi nunca.


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 11 Febrero 2019, 00:27 am
Entiendo.  Pero es lógico que un puntero al ser creado dentro de una función e intentar devolverlo no sirve de nada?.. eso pienso yo, el puntero queda vacío, sin valor.. y cuando se quiera usar como valor retornado no apuntará a ninguna parte...  es asi?


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: Loretz en 11 Febrero 2019, 00:35 am
Creo que a mediados del sigo XX solía verse funciones que creaban un puntero para ser liberados después, por la función que hacía la llamada. Mira, te pongo un ejemplo:

Citar
int* RetornaUnaSuma(int a, int b)
{       

    int * suma = new int; // IMPORTANTE !!! NO OLVIDARSE DE LIBERAR LA MEMORIA CON delete
                          // INSISTO, ES MUY IMPORTANTE, Y NO VAYAIS A USAR free, debe ser delete !!
    *suma = a + b;
    return suma;
}

int main() {
    int a = 1;
    int b = 2;

    int* suma = RetornaUnaSuma(a, b);

    std::cout << "*suma == " << *suma << '\n';

    // ah! y como soy muy aplicado y leo los comentarios de todas las funciones que uso:
    delete suma;
}



Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 11 Febrero 2019, 16:03 pm
Loretz, el codigo que pusiste funciona, entonoces tengo un caos en la cabeza jaja...
Cuando retorna un valor puntero o un valor comun y silvestre no se pierde el valor retornado, entonoces qué pasa con la variable declarada dentro de la funcion?.. no se destruye?...

Lo digo porque, en el caso de retornar un int, supongo que pasaría una copia y no sería problema, pero cuando retornas un int * no estarías retornando una copia... estarias retornando un puntero a la declaracion hecha dentro de la funcion...



Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: Loretz en 11 Febrero 2019, 16:23 pm
Citar
Cuando retorna un valor puntero o un valor comun y silvestre no se pierde el valor retornado
?
 - Tanto para valores comunes como para punteros: si el valor retornado se lo asignas a alguna variable no se perderá, si no se lo asignas a nadie se perderá.

Citar
... entonoces qué pasa con la variable declarada dentro de la funcion?.. no se destruye?...
- Sí, en todos los casos se destruye, no importa qué tipo de variable sea.

Lo que pasa es que las funciones (que devuelven algo), retienen una copia temporal del valor devuelto. Sería estos pasos:
Código:
int f() { int i=5; return i; }

  - al llegar a } se hace una copia de i para ser devuelta, y luego muere i.
  - al invocar a f() se dispone (efímeramente) de ese valor temporal devuelto.

(esto no es realmente así porque el C++ exige que los compiladores implementen lo que se llama "return value optimization", pero eso nosotros no lo vemos, es un truco del compilador).

Una variable puntero (un int* o un cosa*) tiene una vida local, como cualquier otra variable, pero la dirección de memoria accedida con new (o malloc, o todos sus parientes), es eterna, se libera con delete (o free) por el programador, o se libera al sistema cuando termina el programa.

Cuando haces:
Código:
int j = f();

j en este caso va a tener el valor devuelto por f(), que es ese temporal.

Lo mismo cuando una variable es un puntero, igual.

Si ese temporal devuelto no se lo asignas a nadie, (una variable o una referencia), adiós.

Moraleja: un puntero es una variable más, da lo mismo tener un char que un char*, que un mySuperObjeto*; la diferencia es el tipo de "valor" que se les puede asignar. A una variable se le asigna un valor, un 5, por ejemplo; a un puntero se le asigna una dirección de memoria donde hay un 5. Salvo sutilezas, un puntero es una variable más y se comporta como una.



Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: K-YreX en 11 Febrero 2019, 17:48 pm
Hola!
Me he topado con el siguiente escenario:
Código
  1. //En este fragmento al terminar la función, se destruye el contenido de suma y retorna null o vacío, y el puntero suma sigue existiendo en memoria....
  2. int RetornaUnaSuma(int a, int b){
  3.     int * suma = new int;
  4.     *suma = a+b;
  5.     return *suma;
  6. }
  7. //Aqui intento retornar un puntero creado dentro de la misma función...
  8. int * RetornaUnaSuma(int a, int b){
  9.     int * suma = new int;
  10.     *suma = a+b;
  11.     return suma;
  12.     //pero al terminar la función, se destruye el contenido y retorna una dirección de memoria sin contenido, el puntero sigue existiendo..
  13. }
  14.  
Según yo es mala práctica declarar punteros que quieras retomar dentro de una función o un ámbito...  bueno aparte de mala practica no funciona el retorno...Es como lo pienso?...
Probablemente se pueda devolver punteros cuando éstos mismos ingresan como argumento...
La función sería:
Código
  1. //Segun yo esto debe funcionar...
  2. int * RetornaUnaSuma(int * pSuma, int a, int b){
  3.     *pSuma = a+b;
  4.     return pSuma;
  5. }
  6.  
Que opinan...
Saludos.

Respecto al primer mensaje, te dejo tres variantes para analizar cada una de ellas.
  • Variante 1
    La más simple, una función que suma los parámetros en un entero y lo retorna.
    Código
    1. int sumar(int a, int b){
    2.    int suma = a + b;
    3.    return suma;
    4. }
    En este caso no hay mucha complicación, la variable <suma> que es de ámbito local se destruye al terminar la función.

  • Variante 2
    Usando punteros. Creamos un puntero a entero donde se va a guardar la suma y retornamos el puntero .
    Código
    1. int* sumar(int a, int b){
    2.    int *psuma = new int (a + b);
    3.    return psuma;
    4. }
    En este caso como estamos usando memoria dinámica al usar <new>, tenemos que liberar luego la memoria con <delete>. Como la función retorna el puntero no hay problema porque esa función se tiene que asignar a un puntero a entero en otro sitio (ya sea en el <main> o en otra función). Entonces liberamos la memoria en ese sitio:
    Código
    1. int main(){
    2.    int *psuma = sumar(2,2);
    3.    // usas el puntero psuma
    4.    delete psuma;
    5. }

  • Variante 3
    Esto es lo que has implementado en primer lugar y la opción más rara y menos apropiada. Crear un putero a entero donde guardar la suma y retornar el valor de la suma .
    Código
    1. int sumar(int a, int b){
    2.    int *psuma = new int (a + b);
    3.    return *psuma;
    4. }
    En este caso tenemos un problema con la memoria dinámica. Una de las cosas que tiene la memoria dinámica es que cuando se crea un objeto/variable usando esta, el objeto/variable siempre debe estar apuntado por algún puntero. Si deja de ser apuntado por alguien, se pierde. Entonces aquí cuando acaba la función retorna el valor de la suma, el cual es correcto, pero se pierde el puntero <psuma> por lo que ya no se puede liberar.
    Código
    1. int main(){
    2.    int suma = sumar(2,2);
    3.    // usamos la variable suma pero...
    4.    // hemos perdido el puntero local de la funcion
    5. }

    Si quieres profundizar un poco el tema de los valores de retorno, que se guarde temporalmente el valor que se devuelve antes de ser destruido puedes indagar un poco dentro del lenguaje ensamblador. Puedes ver una llamada a función sencilla como esta para ver como la función guarda en un registro el valor de retorno. Entonces cuando la función ha acabado, el valor de retorno está guardado en un registro que debe ser guardado en otro sitio antes de volver a usar ese registro, por eso hay que asignarlo a una variable. Suerte :-X


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 11 Febrero 2019, 18:59 pm
Gracias por las respuestas a ambos.. me ha quedado mucho mas claro ahora con lo ultimo que han dicho..

Una ultima duda:

Código
  1.  
  2. int * sumar(int a, int b){
  3.     int * psuma = new int;
  4.     *psuma = a + b;
  5.     return psuma;
  6.  
  7. }
  8.  
  9. int main (){
  10.     int a = 5;
  11.     int b = 10;
  12.  
  13.     int * psuma = new int;    //duda con el nombre psuma
  14.     psuma = sumar(a, b);
  15.  
  16.     delete psuma;                //aqui se elimina psuma, pero cual?, el declarado
  17.                                         //en main solamente o los dos?
  18.  
  19. }
  20.  
  21.  

Que pasa en el siguiente caso ?

Código
  1.  
  2. int * sumar(int a, int b){
  3.     int * psuma = new int;
  4.     *psuma = a + b;
  5.     return psuma;
  6.  
  7. }
  8.  
  9. int main (){
  10.     int a = 5;
  11.     int b = 10;
  12.  
  13.     int * punteroSuma = new int;    //modificado el nombre
  14.     punteroSuma = sumar(a, b);
  15.  
  16.     delete punteroSuma;      //aqui se elimina psuma, punteroSuma o ambos?
  17. }
  18.  
  19.  


Gracias!


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: K-YreX en 11 Febrero 2019, 19:39 pm
Se eliminan ambos y ahora te explico.
Cuando se elimina un puntero, no eliminas el puntero como tal, sino que liberas la memoria a la que apunta el puntero. Cuando asignas un puntero a otro, ambos apuntan a la misma dirección de memoria. Entonces con eliminar uno de los dos, ya queda liberada la memoria.

Código
  1. int* sumar(int a, int b){
  2.    int *suma = new int(a+b); // reservas memoria para un entero
  3.    return suma;
  4. }
  5.  
  6. int main(){
  7.    int *psuma = sumar(2,2); // psuma apunta a la misma memoria que apuntaba suma
  8.    delete psuma; // liberamos la memoria a la que apunta psuma que es la misma que suma
  9. }


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 11 Febrero 2019, 21:54 pm
entiendo, entiendo...

tengo que imaginar al delete como un interruptor que desactiva el espacio de memoria
y al new como que lo activa..  podría ser así la abstracción.

Gracias nuevamente!! : :o :)


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: Loretz en 12 Febrero 2019, 01:36 am
Citar
tengo que imaginar al delete como un interruptor que desactiva el espacio de memoria y al new como que lo activa..
Casí que es así, no vas a encontrar "activar" pero sí "reclamar", e incluso "allocar" (puaj), la palabra apropiada en el lenguaje C++ es el verbo inglés "allocate".

Si te fijas en la documentación (por ejemplo, en un sitio que no es el del comité estándar pero que todo el mundo tiene a mano: https://en.cppreference.com/w/cpp/memory/new/operator_delete (https://en.cppreference.com/w/cpp/memory/new/operator_delete)
Verás que el operator delete es toda una familia de funciones, dedicadas a liberar (deallocate) la memoria que se haya pedido con su correspondiente new.

Como ves, delete necesita el nombre de una variable para saber qué liberar, si no hay variable no habrá delete. Por ejemplo, una forma de no liberar nunca memoria asignada (capturada, allocated) con new puede ser:
Código:
void f() { new int; }
Aquí new int; reserva un espacio sizeof(int) en la memoria libre (digamos... 4 bytes), que jamás podrá liberar ningún delete, porque ni nombre tiene el pobre.

Haciendo algo más razonable:
Código:
void g() { int* i = new int; }
Ahora, al salir de g(), la variable i va a morir (es una variable local que termina su vida acotada a su ámbito), pero una copia temporal de esa i queda para ser devuelta como un temporal por la llamada a g().

Por eso es que tiene sentido hacer:
Código:
int* p = g();
porque ahora p va a tener el valor de ese temporal que devolvió g().

Si se hubiera hecho sólo:
Código:
g();
una llamada a g() sin asignar su valor devuelto a ninguna variable de tipo int*, se hubieran perdido esos 4 bytes irremediablemente.

pero después de
Código:
int* i = new int;
se podría hacer perfectamente
Código:
int* j = i;
int* k = j;
de este modo los tres punteros apuntan al mismo espacio de memoria. E invocando un delete en cualquiera de ellos se liberarán esos 4 bytes tomados por new. Puedes hacer, por ejemplo:
Código:
delete k;

Lo que no puedes hacer (bueno, puedes, pero el sistema operativo sabrá tratarte como te mereces) es invocar a delete en más de uno de esos, porque estarías tratando de devolver dos o tres veces la memoria que se reclamó una sola vez. Un new y tres deletes, no te atrevas.

Como este tipo de errores siempre fue frecuente y catastrófico, el C++ actual evita el uso de new / delete, y sanseacabó. unique_ptr y shared_ptr son tus amigos.









Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 12 Febrero 2019, 02:18 am
Perfecto, y esos dos últimos que mencionaste (unique_ptr y shared_ptr) serian punteros inteligentes no ?..


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: CalgaryCorpus en 12 Febrero 2019, 02:27 am
Hay un error en lo especificado antes:

En este codigo,

Código
  1.  
  2. int * sumar(int a, int b){
  3.     int * psuma = new int;
  4.     *psuma = a + b;
  5.     return psuma;
  6.  
  7. }
  8.  
  9. int main (){
  10.     int a = 5;
  11.     int b = 10;
  12.  
  13.     int * punteroSuma = new int;    //modificado el nombre
  14.     punteroSuma = sumar(a, b);
  15.  
  16.     delete punteroSuma;      //aqui se elimina psuma, punteroSuma o ambos?
  17. }
  18.  
  19.  


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: CalgaryCorpus en 12 Febrero 2019, 02:28 am
Hay un error en lo especificado antes:

En el codigo que preguntas

Código
  1.  
  2. int * sumar(int a, int b){
  3.     int * psuma = new int;
  4.     *psuma = a + b;
  5.     return psuma;
  6.  
  7. }
  8.  
  9. int main (){
  10.     int a = 5;
  11.     int b = 10;
  12.  
  13.     int * punteroSuma = new int;    //modificado el nombre
  14.     punteroSuma = sumar(a, b);
  15.  
  16.     delete punteroSuma;      //aqui se elimina psuma, punteroSuma o ambos?
  17. }
  18.  
  19.  

al asignar punteroSuma el resultado de sumar(), se pierde el valor que habias asignado antes.
Esa memoria no la estas liberando, ni se liberara en el delete del final.


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 13 Febrero 2019, 04:28 am
No te entendi, cual es el valor que se pierde que había asignado antes?..

EDIT:

Ah!, te refieres a que no debí haber escrito :

Código
  1. int * punteroSuma = new int;

sino:

Código
  1. int * punteroSuma = sumar(a, b);

directamente, verdad?...

Es como que cuando solicité memoria al declarar punteroSuma, esa memoria la dejé botada y pasé el puntero al valor de retorno de Suma.. creo que a eso te refieres...



Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: K-YreX en 13 Febrero 2019, 05:13 am
Exacto, sobra el reservar memoria en el <main>. Imagina que cuando usas <new> compras un almacén para guardar "datos" y cuando ya no lo necesitas usas <delete> para venderlo. Un puntero guarda la dirección del almacén.

En tu caso la función <sumar()> compra un almacén, guarda la suma en él y te envía la dirección donde tienes tu almacén. Entonces en el <main> lo que haces es guardar la dirección que te envía la función <sumar()> en el puntero nuevo. No necesitas comprar otro almacén. Porque si compras otro almacén en el <main> y donde tienes guardada la dirección del nuevo almacén, guardas la dirección del almacén que compraste en la función <sumar()>, ya no sabes donde está el último almacén que has comprado. Y si no sabes donde lo tienes, no puedes venderlo... :-\

No sé si queda claro con esto pero es el mejor símil que se me ha ocurrido :-X


Título: Re: Punteros fantasmas... (asi le puse yo)..
Publicado por: digimikeh en 13 Febrero 2019, 13:15 pm
buen punto... lo tendré en cuenta la siguiente vez..  :)