Punteros y Memoria

Por Ariel Parra

Punteros (Pointers)

Un puntero también conocido como apuntador es una variable que almacena la dirección de memoria de una variable u objeto. Los 3 propósitos principales son:

  1. para asignar nuevos objetos en el heap
  2. pasar funciones a otras funciones
  3. para iterar sobre elementos en matrices u otras estructuras de datos.

Declaración de punteros:

int n = 5;
int *p = nullptr; // se lee como: la variable 'p' es puntero de tipo entero inicializado en nulo
p = &n; //variable 'p' almacena la dirección de un entero 'n'
cout << *p; //accedemos a su valor con el operador de desrreferencia '*'
cout << p; // mostrara su dirección de memoria
CPC Γα=Ω5
CPC Γα=Ω5

Referencias (References)

Una referencia, como un puntero, es una expresión que almacena la dirección de un objeto que se encuentra en la memoria. A diferencia de un puntero, una referencia después de su inicialización no puede hacer referencia a un objeto diferente ni establecerse como nula.
Declaración de referencias (operador ampersand):

int n = 5;
int &n1 = n; //alias

¿Qué salida tendra el cout?

int n = 5; 
int *p = &n; 
cout<< &n << n << p << *p;
CPC Γα=Ω5

Vectores con punteros

los vectores apuntan a una dirección de memoria que es el inicio de este.

int vec[3] = { 10, 20, 30 };
int* ptr;
ptr = vec; // ptr=&vec[0]; 
for(int i=0;i<3;i++){
    cout<< ptr[i];
}

Los strings al ser un vector de caracteres es lo mismo

char *s = "ola";
cout<< *s << s[1] << *(s+2);
CPC Γα=Ω5

Paso de Parametros

Con Punteros:

void squarePtr(int *n){
    *n *= *n;
}
int n = 5; int *ptr = &n;
squarePtr(&n); squarePtr(ptr);

Por referencia:

void squareRef(int& n){
    n *= n;
}
squareRef(n);
CPC Γα=Ω5

Memoria dinámica

Permite asignar y liberar memoria en ejecución, se usa cuando no se conoce desde antes cuánto espacio de memoria se necesitará, como cuando el tamaño de una estructura depende de la entrada del usuario.

int main(){
    int n; cin >> n;
    
    int* vecC = (int*)malloc(n * sizeof(int)); // en C
    int* vecCpp = new int[n]; // en C++

    int size = sizeof(vecC) / sizeof(vecC[0]); // tamaño del vector en C

    free(vecC); // en C
    delete[] vecCpp;// en C++, también se puede: delete []vec;
return 0;
}
CPC Γα=Ω5

Funciones de tipo puntero

int *crearVec(int tam){
    int* aux = new int[n]; 
    for(int i=0;i<tam;i++)
        aux[i]=i;
    return aux;
}
int main(){
    int n; cin>>n;
    int* vec = crearVec(n); 
    for(int i=0;i<n;i++)
        cout << vec[i];
    delete[] vec;
return 0;
}
CPC Γα=Ω5

Aritmética de Punteros

La memoria al ser declarada contigua permite el uso de operaciones aritméticas con punteros.
Ejemplo:

int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;  // Apunta a arr[0]
ptr++;  // Ahora apunta al segundo elemento arr[1] = (20)
cout << *(ptr++); // ¿Qué mostrara?

Esto se puede expandir a un uso de iteradores en vectores:

int vec[] = {10, 20, 30};
int *ptr = vec;
for(int i=0; i < 3;++i)
    cout << *(ptr+i); // ptr[i];
CPC Γα=Ω5

Aritmética de Punteros en matrices:

const int REN=3, COL=3;
int mat[REN][COL]={ { 10, 20, 30 },
                    { 40, 50, 60 },
                    { 70, 80, 90 } };
int* ptr = &mat[0][0]; 
for(int i=0; i< REN ;i++){
    for(int j=0; j< COL ;j++){
        cout << *(ptr + i * COL+ j) ;
    }
}

¿Creen que este código funcione?

int vec[] = {10, 20, 30}; int *ptr = vec;
for(int i=0; i < 3; ++i) cout << i[ptr];
CPC Γα=Ω5

Distribución de la memoria

  • stack: almacena variables locales
  • heap: memoria dinámica para que la asigne el programador
  • data: almacena variables globales, separadas en inicializadas y no inicializadas
  • text: almacena las funciones y el código que se está ejecutando
CPC Γα=Ω5

El Stack

El segmento de stack en la parte superior de la memoria. Cada vez que se llama a una función, el CPU le asigna algo de memoria al stack. Cuando se declara una nueva variable local, se le asigna más memoria de stack a esa función para almacenar la variable. Estas asignaciones hacen que la stack crezca hacia abajo. Después de que la función retorna, la memoria de stack de esta función se desasigna, lo que significa que todas las variables locales dejan de ser válidas. La asignación y desasignación de memoria de stack se realiza automáticamente.

CPC Γα=Ω5

Ejemplo de cómo se ve la memoria de stack cuando se ejecuta el código correspondiente:
#c

CPC Γα=Ω5

El Heap

A diferencia de la memoria de stack, la memoria del heap es asignada explícitamente por los programadores y no se desasignará hasta que se libere explícitamente. (Memoria dinámica).

#c

CPC Γα=Ω5

Cuando usar el Heap o Stack

Usa el stack cuando:

  • No deseas desasignar variables usted mismo.
  • Necesitas velocidad (la CPU gestiona eficientemente el espacio).
  • El tamaño variable es estático.

Usa el Heap cuando:

  • Necesitas una gran cantidad de espacio (prácticamente sin límite de memoria).
  • No le importa un acceso un poco más lento (pueden ocurrir problemas de fragmentación).
  • Quiere pasar objetos (globales) entre funciones.
  • Te gusta gestionar las cosas tú mismo.
  • El tamaño variable podría ser dinámico.
CPC Γα=Ω5

Complicaciones

CPC Γα=Ω5

Colisión entre Heap y Stack

void recursion(int depth) {
    int largeArray[10000];  // Asignación grande en el stack
    recursiveFunction(depth + 1);
}

int main() {
    // Asignación de un bloque grande de memoria en el heap
    int *heapMemory = (int*)malloc(100000000 * sizeof(int));

    // Iniciar la función recursiva que consumirá el stack
    recursiveFunction(1);

    // Liberar la memoria en el heap (aunque no se alcanzará si ocurre una colisión)
    free(heapMemory);
    return 0;
}
CPC Γα=Ω5

Pointer to a Constant (const int* ptr)

No puedes cambiar el valor apuntado, pero puedes cambiar a qué dirección apunta el puntero.

int x = 10;
const int* ptr = &x; // ptr es un puntero a una constante int

// *ptr = 20;  // Error: no se puede modificar el valor a través de ptr
x = 20;       // Esto es válido, el valor de 'x' se puede modificar directamente

int y = 30;
ptr = &y;     // Esto es válido, ptr puede apuntar a otro entero
CPC Γα=Ω5

Constant Pointer (int* const ptr)

Puedes cambiar el valor apuntado, pero no puedes cambiar a qué dirección apunta el puntero.

int x = 10;
int* const ptr = &x; // ptr es un puntero constante

*ptr = 20;   // Esto es válido, el valor de 'x' se puede modificar a través de ptr

// int y = 30;
// ptr = &y;  // Error: no se puede cambiar la dirección a la que apunta ptr
CPC Γα=Ω5

void*

int n = 42;
void* ptr = &n;  // ptr es un puntero genérico que apunta a un int

// Necesitas hacer un cast para desreferenciar:
int* intPtr = (int*)ptr;

Memory leaks

int main() {
    int *ptr = new int[100];
    return 0;
}
CPC Γα=Ω5

Conclusión

CPC Γα=Ω5

Problemas

Resuelve los siguientes problemas usando aritmetica de punteros, memoria dinámica, etc.

CPC Γα=Ω5

Referencias

CPC Γα=Ω5
CPC Γα=Ω5
CPC Γα=Ω5
CPC Γα=Ω5
CPC Γα=Ω5

usar la página: https://pythontutor.com/cpp.html#mode=edit

Dibujar en el pizarron una tabla donde el lado izquierdo tenga variables y del lado derecho sus direcciones (0xetc)

mostrar mi código: https://github.com/ArielParra/Proyecto_Estructuras donde tengo goto para solucionar el problema

Preguntar que falta en memory leak