Foro de elhacker.net

Programación => Programación C/C++ => Mensaje iniciado por: Julia13 en 26 Mayo 2021, 00:44 am



Título: Punteros en c++
Publicado por: Julia13 en 26 Mayo 2021, 00:44 am
Alguien me pude decir como funcionan los punteros y con un ejemplo por favor


Título: Re: Punteros en c++
Publicado por: DtxdF en 26 Mayo 2021, 01:52 am
Hola @Julia13

En C, un puntero es una variable que hace referencia a una dirección de memoria, que a su vez hace referencia a un dato.

Teniendo el siguiente código:

Código
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main(void)
  5. {
  6.    int x;
  7.    int *y = &x;
  8.  
  9.    x = 3;
  10.  
  11.    printf("%d\n", *y);
  12.  
  13.    return EXIT_SUCCESS;
  14. }

Se crean dos variables. Una de tipo entero y otra un puntero que apunta a un entero.

Primero se asigna el valor 3 en la variable x, y después se asigna el valor de x a y. Se puede notar claramente un operador *, que desreferencia y permite la lectura y la escritura de lo que haya allí en esa variable.

En realidad esta no es la única forma de asignar un valor de una variable a otra.

Código
  1. y = &x;

Lo que hará el operador & será obtener la dirección de x y se la asignará a y, entonces ahora las dos variables apuntarán al mismo dato, pero para acceder a él, es de forma distinta.

Los punteros también son muy útiles para manejar arreglos y que a su vez pueden ser pasados a funciones.

Código
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int sum_array(int *array, int length)
  5. {
  6.    int result = 0;
  7.  
  8.    while (length--)
  9.        result += array[length];
  10.  
  11.    return result;
  12. }
  13.  
  14. int main(void)
  15. {
  16.    int n_array[] = { 1, 2, 3, 4 };
  17.    int result = sum_array(n_array, sizeof(n_array)/sizeof(n_array[0]));
  18.  
  19.    printf("%d\n", result);
  20.  
  21.    return EXIT_SUCCESS;
  22. }

Lo que sucede en este pequeño programa es que se pasa a la función dos argumentos: el primero es la dirección donde comienza el arreglo y el segundo argumento es la cantidad de elementos que contiene.

En realidad no se ve prácticamente nada de lo que sucede. Veamos este pequeño programa en nasm:

Código
  1. section .data
  2.    n_array: dq 1, 2, 3, 4
  3.    n_array_length: equ ($-n_array)/8
  4.  
  5. section .text
  6.    global _start
  7. _start:
  8.    mov rsi, n_array
  9.    mov rcx, n_array_length
  10.    xor rax, rax
  11.    xor rbx, rbx
  12.  
  13. .loop:
  14.    add rax, [rsi+rbx*8]
  15.    inc rbx
  16.    loop .loop

Si mira en la sección de datos se creó n_array, que básicamente es el inicio del arreglo de datos que se ven allí. Si mira la línea 8 se coloca la dirección de inicio en el registro rsi, y mucho después en el bucle .loop, se comienza a obtener los datos para sumarlo en el acumulador rax. Vea [rsi+rbx*8], prácticamente sería como un equivalente al operador * de C, ya que se obtiene ese dato al que apunta el resultado de la operación de sumar el registro rsi con rbx (multiplicado por 8, ya que cada dato del arreglo n_array pesa 8 bytes).

Este programa no se debe ejecutar como uno común, ya que nunca le pide al sistema que salga, pero para estos fines, está perfecto. Hay que mirar desde el depurador, que en mi caso es gdb.

Código:
(gdb) break _start
Breakpoint 1 at 0x201160
(gdb) r
Starting program: /tmp/main

Breakpoint 1, 0x0000000000201160 in _start ()
(gdb) disassemble _start
Dump of assembler code for function _start:
=> 0x0000000000201160 <+0>:     movabs $0x202180,%rsi
   0x000000000020116a <+10>:    mov    $0x4,%ecx
   0x000000000020116f <+15>:    xor    %rax,%rax
   0x0000000000201172 <+18>:    xor    %rbx,%rbx
End of assembler dump.
(gdb) disassemble '_start.loop'
Dump of assembler code for function _start.loop:
   0x0000000000201175 <+0>:     add    (%rsi,%rbx,8),%rax
   0x0000000000201179 <+4>:     inc    %rbx
   0x000000000020117c <+7>:     loop   0x201175 <_start.loop>
End of assembler dump.
(gdb) nexti
0x000000000020116a in _start ()
(gdb)
0x000000000020116f in _start ()
(gdb)
0x0000000000201172 in _start ()
(gdb)
0x0000000000201175 in _start.loop ()
(gdb)
0x0000000000201179 in _start.loop ()
(gdb)
0x000000000020117c in _start.loop ()
(gdb)
0x0000000000201175 in _start.loop ()
(gdb)
0x0000000000201179 in _start.loop ()
(gdb)
0x000000000020117c in _start.loop ()
(gdb)
0x0000000000201175 in _start.loop ()
(gdb)
0x0000000000201179 in _start.loop ()
(gdb)
0x000000000020117c in _start.loop ()
(gdb)
0x0000000000201175 in _start.loop ()
(gdb)
0x0000000000201179 in _start.loop ()
(gdb)
0x000000000020117c in _start.loop ()
(gdb)
0x000000000020117e in ?? ()
(gdb) print $rax
$1 = 10
(gdb)

Se recorre todo el arreglo haciendo la misma operación y al final el resultado estará en el registro rax.

Espero le haya servido.

~ DtxdF


Título: Re: Punteros en c++
Publicado por: Loretz en 26 Mayo 2021, 22:41 pm
Bueno, no, no está bien.

    int x;
    int *y;  // (1) y es un puntero sin inicializar
 
    x = 3;
    *y = x;  // (2) se usa una dirección de memoria que no fue inicializada. 

(1) debería llamar la atención
(2) es un error.




Título: Re: Punteros en c++
Publicado por: DtxdF en 26 Mayo 2021, 22:47 pm
@Loretz

Supongo que no lo has probado. Porque parece que no.

¿Cómo asignarías un valor a un puntero?

(https://imgur.com/fTQkXHI.png)

~ DtxdF


Título: Re: Punteros en c++
Publicado por: Eternal Idol en 26 Mayo 2021, 23:23 pm
Supongo que no lo has probado. Porque parece que no.

No hace falta probarlo, estas desreferenciando un puntero indefinido como ya te dijeron, puede funcionar DE CASUALIDAD miles de veces.

c:\src\badptr.cpp(10) : warning C4700: uninitialized local variable 'y' used

¿Cómo asignarías un valor a un puntero?

Un ejemplo elemental:
Código
  1. int *y = new int;


Título: Re: Punteros en c++
Publicado por: DtxdF en 26 Mayo 2021, 23:30 pm
Comprendo Eternal Idol y Loretz. No me había fijado, gracias por la aclaratoria.

~ DtxdF


Título: Re: Punteros en c++
Publicado por: temporalEotw en 27 Mayo 2021, 17:20 pm
Hola, ya veo que te dieron una explicación muy detallada arriba, tocando temas técnicos y tal. Pero trataré de enseñarte los punteros de esta forma... Un puntero no guarda datos específicamente, sino, direcciones de memoria.

Ejemplo:

Tú tienes una casa y la dirección de la casa en un papel, así que el puntero vendría siendo ese papel. Este no tiene la casa pero si su dirección.

Esto te podría servir para apuntar una variable en constante cambio, solo deberías asignarle la dirección al puntero una sola vez y te ahorrarías el tener que reasignarle el valor a cada rato (en caso de que sea una variable).

Siguiendo el ejemplo de arriba:

Resulta que a la casa le estas cambiando el color cada cierto tiempo, ya que tienes la dirección puedes ir y ver esos cambios sin ningún problema o paso de por medio.

En cambio, si hubieras guardado la casa en una variable, al revisarla te darías cuenta de que la casa no ha cambiado de color (cuando se supone que la original sí) así que te tocaría volver a guardar la casa en esa variable para poder apreciar el cambio.


Título: Re: Punteros en c++
Publicado por: MAFUS en 29 Mayo 2021, 12:55 pm
Puntero a lo easy.

Un puntero es un número (cómo todo dentro de un ordenador) y ese número representa una dirección de memoria.

Por ejemplo:
Código
  1. #include <stdio.h>
  2.  
  3. int main() {
  4.    printf("%d", *(int*)0x61FE1C);
  5. }

En el ejemplo anterior directamente le digo a C que me muestre qué hay en la dirección de memoria 61FE1C. ¿Se puede? Sí. Por cierto, ese casting (int*) es necesario para convertir el literal en un puntero. El * que hay delante nos muestra el contenido.

* Un poco más avanzado: En este caso he hecho un programa previo para ver dónde residía la memoria de datos del programa y pudiera apuntar ahí con este ejemplo. Los sistemas operativos actuales y los microprocesadores funcionan por capas. No van a dejar que un programa de usuario pueda leer o escribir fuera de su área designada. Antes sí que se podía, o en sistemas sin protección sí puede hacerse.

Podemos usar una variable para alojar esa dirección de memoria del ejemplo anterior:
Código
  1. #include <stdio.h>
  2.  
  3. int main() {
  4.    int *p = (int*)0x61FE1C;
  5.    printf("%d", *p);
  6. }

Aquí asignamos el literal a una variable puntero. Las variables puntero se denotan por anteponer el asterisco (*) delante del nombre durante su definición o declaración. Para ver la definición y declaración por separado sería así:

Código
  1. #include <stdio.h>
  2.  
  3. int main() {
  4.    int *p;
  5.    p = (int*)0x61FE1C;
  6.    printf("%d", *p);
  7. }

Nótese que cuándo se hace la definición (se le da valor) no se usa el asterisco (*), pues éste tiene la función, en sus posteriores usos, de conseguir el valor guardado en esa posición de memoria, que en este caso es la posición 0x61FE1C.

Pero cómo hemos dicho el conjunto sistema operativo/microprocesador no te deja apuntar a zonas arbitrarias de la memoria, el sistema te la tiene que dar y ahí es cuando entra C que es quien negocia con el sistema para conseguir memoria libre; nosotros no debemos preocuparnos por nada de eso (afortunadamente). Por tanto cuando haces

Código:
int n;

C se encarga de reclamar una zona de memoria donde cabe un entero y el sistema se la otorga. ¿Cuál? La verdad es que no nos importa. Ídem para

Código:
int *n = malloc(sizeof(int));

Hace lo mismo: pide al sistema una zona de memoria dónde quepa un entero y éste, si tiene, nos la da.

* Un poco más avanzado: la memoria de un programa en C está dividida en sectores que son la memoria de programa, la memoria de la pila y la memoria del montón (heap). Hay más. La pila es donde caen las variables normales como int, char, double, arrays, structs, etcétera y es C el encargado de manejarlas. En el montón van a parar los malloc, calloc y toda esa família; C no entra ahí y es el propio programador el encargado de comprobar que se ha tomado memoria, de liberarla para futuros usos demás menesteres de administración.

Lo normal de un puntero es recibir la dirección de memoria de otra variable:
Código:
int n;
int *p = &n

El ampersand (&) es un operador que (en este caso) dice 'La dirección de'.

También es muy importante de los punteros el tipo de dato al que apuntan. Has visto en todos estos ejemplos cómo siempre han sido a entero (int *), pero pueden ser de cualquier tipo de dato: char *, double *, struct mi_estructura *, etcétera. Esto es debido  a que los diferentes tipos de datos ocupan tamaños en bytes diferentes en memoria y el compilador necesita saber cuánto ocupa para recuperarlo o para guardar algo en él. Por ejemplo un char suele ser de 1 byte y un entero de 4 bytes. Si los punteros no especificaran el tamaño con su tipo podríamos guardar un número muy grande en una posición dónde se guarda un char machacando datos vecinos: intentaríamos guardar 4 bytes dónde sólo cabe 1.

Y bueno, esto es lo básico de los punteros. Hay más cosas: punteros como argumentos en funciones y porqué usarlos; similitudes y diferencias entre punteros y arrays; punteros a funciones (sí, también se pueden apuntar a funciones); punteros a punteros (por loco que parezca muchas veces son necesarios). Pero eso ya para más adelante. Ahora intenta comprender sus bases.


Título: Re: Punteros en c++
Publicado por: Eternal Idol en 29 Mayo 2021, 15:06 pm
* Un poco más avanzado: En este caso he hecho un programa previo para ver dónde residía la memoria de datos del programa y pudiera apuntar ahí con este ejemplo. Los sistemas operativos actuales y los microprocesadores funcionan por capas. No van a dejar que un programa de usuario pueda leer o escribir fuera de su área designada. Antes sí que se podía, o en sistemas sin protección sí puede hacerse.

¿Y si estamos trabajando en macOS (y el ASRL esta involucrado por defecto en cada ejecucion)?  :-\ En Windows si el programa tiene ASLR habilitado (por defecto en proyectos desde Visual Studio 2008) al reiniciar la direccion base tambien cambiara.

Código
  1. int zzz = 5;
  2.  
  3. int main(int argc, const char * argv[])
  4. {
  5.        printf("%p\n", &zzz);
  6.        return 0;
  7. }

Citar
m1-Mac-mini:Debug tester$ ./test
0x10b45fc5c
m1-Mac-mini:Debug tester$ ./test
0x10a7d2c5c
m1-Mac-mini:Debug tester$ ./test
0x10eb4ac5c
m1-Mac-mini:Debug tester$ ./test
0x10fcb8c5c
m1-Mac-mini:Debug tester$ ./test
0x106542c5c
m1-Mac-mini:Debug tester$ ./test
0x10f495c5c
m1-Mac-mini:Debug tester$ ./test
0x1080a5c5c
m1-Mac-mini:Debug tester$ ./test
0x10d075c5c
m1-Mac-mini:Debug tester$ ./test
0x10885ac5c



Mejor dejemos en claro que usar direcciones hardcoded es una mala practica y nadie que este aprendiendo a usar punteros deberia emplearla.


Título: Re: Punteros en c++
Publicado por: MAFUS en 29 Mayo 2021, 18:00 pm
Sí, pero para entender una cosa realmente es mejor empezar desde los cimientos y los cimientos, en punteros, son que es un número que representa una dirección de memoria. Y de ahí para arriba. Estarás de acuerdo conmigo que realmente no se puede entender C sin entender el esquema de memoria. Al menos eso me sirvió a mi: entonces todo fue más fácil. Eso y la traducción a ensamblador de la convención de llamadas a funciones, para entender las variables automáticas.



Supongo que no será el caso de mucha gente que visite el foro pero este lenguaje es muy habitual verlo en microcontroladores y aquí se mapean en memoria muchos registros de configuración y los puertos E/S. En PC usar constantes para acceder a memoria no es útil, pero en el mundo del firmware es la norma.


Título: Re: Punteros en c++
Publicado por: Eternal Idol en 30 Mayo 2021, 02:38 am
Por supuesto aunque entendi que habia sido explicado en este hilo, salvo que asumamos un desconocimiento de lo que es una direccion de memoria. Sin dudas para poder comprender cabalmente el codigo generado y depurarlo en condiciones es necesario profundizar en varias aristas pero juraria que el esquema de memoria o la convencion de llamada no son parte del standard del lenguaje sino que corren por cuenta del compilador bajo la plataforma en cuestion.



Habria que pensar que es mas probable, si que el que pregunto por punteros en C++ mañana compile tu ejemplo (y le de problemas) o que escriba firmware.


Título: Re: Punteros en c++
Publicado por: MAFUS en 30 Mayo 2021, 11:20 am
Entiendo tu postura de dejar las cosas lo más sencillas posibles, pero tal vez esa sencillez no sea suficiente. Estarás de acuerdo conmigo que el problema del entendimiento de los punteros es recurrente en todo el que ha estudiado este lenguaje. Yo, por mi parte, hasta que no vi a los punteros de esa forma no llegué a comprenderlos del todo y por eso, creo yo, que deberían enseñarse de esa forma.

Es cierto que el programa donde se asigna una constante como dirección de memoria no le va a funcionar. Pero sirve de ejemplo para mostrar que ocurre por debajo, y de ahí la construcción progresiva hasta llegar al puntero que recoge una dirección de una variable; es decir, es para que vaya viendo el proceso natural de lo que ocurre en la máquina.

O tal vez es que por mi curiosidad me gusta saber esas cosas.

Tienes razón, tal vez no se dedique a hacer firmwares, pero quien sabe si un estudiante de electrónica si ve este post y está interesado en saber cómo funcionan los punteros en C++. Siempre intento dar las respuestas pensando en un amplio espectro de gente que pueda leerla.

Y si, tienes razón, el esquema de memoria variará de arquitectura a arquitectura y por ende el de las llamadas a funciones. Pero también es un punto a tener en cuenta.


Título: Re: Punteros en c++
Publicado por: H4cker K en 7 Junio 2021, 15:11 pm
Espero que no sea demasiado tarde para contestar pero paso a responder, los punteros es una de las cosas que la verdad mas me costo dominar pero fue porque cuando lei documentacion pues digamos como que no explicaba bien todo al fondo y pus espero hacerme entender porque los voy a explicar como a mi me hubiera gustado que me lo explicaran, bueno, ves las variables? para este ejemplo vamos a tomar una a casa, el valor de la variable sera el numero de personas viviendo ahi, en una casa hay 5 personas viviendo y esa casa es A, sin embargo, una casa tiene una direccion verdad y digamos que dicha direccion sea la Calle Pepe no.245 como ejemplo(obviamente es una direccion falsa) ahora digamos que hay una persona que le quiere enviar una carta a uno de los integrantes de la familia de 5, esa carta tiene que tener la DIRECCION de la casa para poder enviarla asi que tendra que escribir en la carta que es para fulanito de tal para Calle Pepe no.245. Pues bueno lo mismo pasa con las compus, digamos que declaramos la variable "int a = 5"(haciendo el ejemplo de la casa), pero ahora queremos tener algo que no tenga exactamente un valor, sino que tenga una direccion de memoria, es decir, algo que le diga a la compu onta la variable a y como hacemos eso? pues nada mas y nada menos que con los punteros, en programacion c/c++ para saber la direccion de memoria de una variable se utiliza el caracter address-of que es este de aqui& y para que me entienda mejor, zy tu quieres decir la direccion de la casa A del ejemplo anterior como si fuera estilo programacion dirias &casa_A = Calle Pepe no.245 y pues como practica en lo mientras vamos a depurar un sencillo programa con gdb:

Código
  1. #include <stdio.h>
  2.  
  3. int main(void){
  4.        int a = 5;
  5.  
  6.        return 0;
  7. }
  8.  

Una vez compilado este programa(yo lo compilare en linux y para hacerlo desde ahi es con: gcc nombre_programa_c.c -o nombre_programa_compilado), ahora abriremos el gdb con

Código
  1. gdb -q nombre_programa_compilado
  2.  
                                                                 

ahora lo que haremos sera poner un breakpoint justo despues de que la instruccion de la declaracion de a se haya dado y eso es en la linea 5 como podras ver(int a = 5; esta en la linea 4) asi que pondremos un breakpoint en la linea 5  y ejecutaremos el programa con:

Código
  1. (gdb) break 5
  2. Breakpoint 1 at 0x1129: file ejemplo.c, line 6.
  3. (gdb) run
  4.  

yo le puse ejemplo.c al archivo por cierto pero continuando, a tiene un valor de 5 y para comprobarlo desde gdb podemos usar:

Código
  1. (gdb) print a
  2. $1 = 5
  3. (gdb) p a
  4. $2 = 5
  5.  

los $1 y $2 son variables que se guardaran temporalmente, puse print y p porque muchos comandos de gbd estan abreviados y pus eso pero lo que importa es que como dice gdb a tiene un valor de 5 pero ahora queremos saber la direccion de memoria de a y para hacerlo usaremos el caracter &(address-of) antes de a, asi:

Código
  1. (gdb) p &a
  2. $3 = (int *) 0x7fffffffdfcc
  3.  

y como puedes ver la direccion de memoria de a es: 0x7fffffffdfcc(en este ejemplo esta larga porque en donde estoy corriendo el programa es de 64 bits pero en la de 32 bits es de unos 4 bytes pero ahorita no me voy a poner a explicar eso) esta direccion de memoria vendria ser como la direccion de la casa A que era Calle Pepe no.245, si queremos hacer un programa donde ya no tengamos que depurar podemos usar el formato de cadena %p en un printf asi:

Código
  1. include <stdio.h>
  2.  
  3. int main(void){
  4.        int a = 5;
  5.        printf("Direccion de memoria de a es: %p", &a);
  6.        return 0;
  7. }
  8.  

pues ahora zy con todo esto porfin puedo pasar a explicar lo que es un puntero, hasta ahora vimos que podiamos saber la direccion de memoria de a usando el caracter & pero  ahora queremos que haya algo que pueda tener el valor de la direccion de memoria de a y aqui es donde entran los punteros, en c/c++ para declarar los punteros se utiliza el caracter * antes de poner el nombre del puntero, para entenderme mejor veamos un ejemplo:

Código
  1. #include <stdio.h>
  2.  
  3. int main(void){
  4.        int a = 5;
  5.        int *pa;
  6.        pa = &a;
  7.        return 0;
  8. }
  9.  

aqui tenemos a nuestro puntero pa, yo le puse de nombre pa aunque perfectamente se podia declarar como int *a; pero le pongo pa porque me gusta diferenciar punteros de variables cuando se tratan de proyectos complejos, siempre que se declaren punteros recuerda que se usa *<nombre_puntero> y una cosa tambien, es importante siempre indicar el tipo de datos del puntero que en este caso es int porque zy juntas por ejemplo uno char con uno int pues obviamente te va a salir error porque se va malinterpretar a la hora del tamaño de los bytes pero ya teniendo esto en cuenta de que zy vas a usar una variable y un punteros los uses con el mismo tipo pues explicare la parte de pa = &a; esto es asi porque recuerda que los punteros no tienen un valor sino que tienen direcciones de memoria en su lugar y con el caracter & es como indicabamos la direccion de memoria de una variable, asi zy le dices a la compu que pa es igual a la direccion de memoria de a pues te va a entender que te estas poniendo algo para saber onta a(espero que no este cantinfleando a este punto xd) pero bueno, vamos a correr este programa con gdb en la linea que esta despues de la asignacion de la direccion que tendra el puntero pa  y es la linea 7 asi:

Código
  1. (gdb) break 7
  2. Breakpoint 1 at 0x1138: file ejemplo.c, line 7.
  3. (gdb) run
  4.  

y empezaremos a ver los ver que tienen dentro la variable y el puntero:

Código
  1. (gdb) p a
  2. $1 = 5
  3. (gdb) p &a
  4. $2 = (int *) 0x7fffffffdfc4
  5. (gdb) p pa
  6. $3 = (int *) 0x7fffffffdfc4
  7. (gdb) p &pa
  8. $4 = (int **) 0x7fffffffdfc8
  9. (gdb)
  10.  

y como puedes notar a sigue teniedo el valor de 5 pero lo interesante esta en el valor de la direccion de memoria de a y la direccion a la que apunta pa que son las mismas y esa direccion es: 0x7fffffffdfc4 y es de hecho una cosa importante que se me olvido mencionar y es que la direccion a la que apuntara un puntero no sera la misma que la de su direccion de memoria, tomare nuevamente el ejemplo de la casa A, cuando alguien le mando la carta a la casa A el que la mando no estaba en la casa A, estaba en B y sus direcciones no son las misma y es que asi B puede estar en Calle Jose no.456 y que el mande la carta a Calle Pepe no.245 y pues aca pasa lo mismo, pa puede estar en un lado pero tener el donde esta otro lugar y pues hasta aca todo bien pero ahora explicare la parte donde la mayoria se confunden y es esto:

Código
  1. (gdb) p *pa
  2. $5 = 5
  3. (gdb) p pa
  4. $6 = (int *) 0x7fffffffdfc4
  5. (gdb)
  6.  

cuando despues de declarar un putero volvemos a poner el signo * nos referimos al valor de la variable a la que esta apuntando el puntero, pa esta apuntando a la variable a y a tiene el valor de 5 y zy nos referimos al valor de lo apuntado tambien deberia ser 5 y es asi  como lo muestra p *pa, sin embargo cuando no ponemos de nuevo el signo * significa que nos referiremos a la direccion a la que apunta, entonces en resumen:

*pa = valor de lo que esta apuntando
pa = direccion de memoria de lo que esta apuntando

Ahora para aclarar un poco mas esto el siguiente programa lo terminara de hacer:

Código
  1. #include <stdio.h>
  2.  
  3. int main(void){
  4.        int a = 5;
  5.        int *pa;
  6.        pa = &a;
  7.        printf("El valor de la variable a la que apunta pa es: %i", *pa);
  8.        printf("\nEl valor de la variable a es: %i", a);
  9.        printf("\nLa direccion de memoria a la que apunta pa es: %p", pa);
  10.        printf("\nLa direccion de memoria de a es: %p", &a);
  11.        return 0;
  12. }
  13.  

Entonces cuando te sientas confundido con los punteros siempre recuerda que *pa hace referencia al valor de lo que esta apuntando y pa a la direccion de lo que apunta, y todavia esta lo de las funciones pero creo que por el momento es suficiente y por zy acaso, la salida del programa anterior es:

El valor de la variable a la que apunta pa es: 5
El valor de la variable a es: 5
La direccion de memoria a la que apunta pa es: 0x7ffdd29322f4
La direccion de memoria de a es: 0x7ffdd29322f4

y ya como ultimo te dejare aqui unos programas que hacen practicamente lo mismo(a uno nada mas le agregue una comparacion para saber cuando un usuario intenta engañar al programa pero es lo de menos), para que veas la diferencia entre usar y no los punteros:

SIN punteros:

Código
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. int main(void){
  5. int a, c, d, b = 1000;
  6. printf("Bienvenido a cajero automatico 1!!!, tienes 1000 varos");
  7. printf("\n\nOpcion 1: Retirar");
  8. printf("\nOpcion 2: Depositar");
  9. printf("\nOpcion 3: Salir");
  10. printf("\n\n\nEliga una opcion: ");
  11. scanf("%i", &a);
  12. switch(a){
  13. case 1:
  14. c;
  15. printf("\n\nIngrese la cantidad a retirar: ");
  16. scanf("%i", &c);
  17. d = b - c;
  18. printf("\nLa cantidad que quedo es de: %i", d);
  19. system("pause>nul");
  20. break;
  21.  
  22. case 2:
  23. c;
  24. printf("\n\nIngrese la cantidad a depositar: ");
  25. scanf("%i", &c);
  26. d = b + c;
  27. printf("\nLa cantidad que quedo es de: %i", d);
  28. system("pause>nul");
  29. break;
  30.  
  31. case 3:
  32. printf("\n\nSaliendo...");
  33. system("pause>nul");
  34. break;
  35.  
  36. default:
  37. printf("Ingrese algo correcto");
  38. system("pause>nul");
  39. break;
  40. }
  41. return 0;
  42. }
  43.  

CON punteros:

Código
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <stdint.h>
  4.  
  5. struct cajero{
  6. uint16_t cantidad;
  7. uint16_t varia;
  8. uint16_t deposito;
  9. uint16_t numero;
  10. uint16_t *pcantidad;
  11. };
  12.  
  13. void retirar(int *cantidad_retirar, int *valor_o){
  14. struct cajero retir;
  15. if(*cantidad_retirar <= 0){
  16. printf("Ingrese una cantidad valida");
  17. }
  18. else{
  19. if(*cantidad_retirar > *valor_o){
  20. printf("\nNo tienes esa cantidad de dinero");
  21. }
  22. else{
  23. retir.varia = *valor_o - *cantidad_retirar;
  24. printf("\nSe han retirado %u y el saldo final es de: %u", *cantidad_retirar, retir.varia);
  25. }
  26. }
  27. }
  28.  
  29. void depositar(int *depositar_cantidad, int *valor_o){
  30. struct cajero deposite;
  31. if(*depositar_cantidad <= 0){
  32. printf("Ingrese una cantidad valida");
  33. }
  34. else{
  35. deposite.deposito = *depositar_cantidad + *valor_o;
  36. printf("\nSe ha depositado satisfactoriamente");
  37. printf("\nAhora tienes en tu cuenta %u", deposite.deposito);
  38. }
  39. }
  40.  
  41. void salir_x(void){
  42. printf("\nAviso!: Esta saliendo del programa");
  43. }
  44.  
  45. int main(void){
  46. struct cajero opcion;
  47. opcion.cantidad = 1000;
  48. printf("Bienvenido a cajero automatico 2!!!, tienes 1000 varos");
  49. printf("\n\nOpcion 1: Retirar");
  50. printf("\nOpcion 2: Depositar");
  51. printf("\nOpcion 3: Salir");
  52. printf("\n\n\nEliga una opcion: ");
  53. scanf("%u", &opcion.numero);
  54. switch(opcion.numero){
  55. case 1:
  56. opcion.pcantidad = &opcion.cantidad;
  57. printf("\n\nIngrese la cantidad a retirar: ");
  58. scanf("%u", &opcion.varia);
  59. retirar(&opcion.varia, opcion.pcantidad);
  60. getch();
  61. break;
  62.  
  63. case 2:
  64. opcion.pcantidad = &opcion.cantidad;
  65. printf("\n\nIngrese la cantidad a depositar: ");
  66. scanf("%u", &opcion.varia);
  67. depositar(&opcion.varia, opcion.pcantidad);
  68. getch();
  69. break;
  70.  
  71. case 3:
  72. salir_x();
  73. getch();
  74. return 0;
  75.  
  76. default:
  77. printf("Ejecute algo correcto");
  78. getch();
  79. return -1;
  80. }
  81. return 0;
  82. }
  83.  

Nota: si se usan los punteros es para evitar usar mucha memoria porque creeme que muchas veces en programacion vas a tener que usar el mismo valor de una variable dentro de otras funciones pero cuando son proyectos de muchas lineas de codigo puede llenar mucha memoria y con lo punteros se evita todo ese gasto de energia utilizando solamente referencias a en que parte del programa esta la variable y por el momento es todo de mi parte, zy te quedaron dudas te dejo este link de este profe:

https://www.youtube.com/watch?v=5TS1tdBcIoM&list=PLcYO0Mqfc4g87Y-XcZ0TWwrSiKlNXo0kh&index=9