- Memoria y direcciones
La CPU accede a los datos almacenados en la memoria del sistema. Para gestionar eficientemente el espacio de memoria (comúmente 8GB, 16GB o 32GB), se divide en unidades de memoria individuales, cada una del tamaño de un byte.
Cada unidad de memoria se identifica con una dirección única. En lenguaje C, estas direcciones se denominan punteros. La CPU utiliza estas direcciones para localizar rápidamente los datos necesarios.
1.1 Entendiendo la direccionamiento
La CPU se comunica con la memoria mediante buses físicos: bus de direcciones, bus de datos y bus de control. En una arquitectura de 32 bits, existen 32 líneas de dirección, lo que permite generar 232 direcciones únicas.
Los buses transmiten señales eléctricas que se convierten en secuencias binarias. Estas secuencias constituyen las direcciones de memoria, que permiten a la CPU acceder a ubicaciones específicas.
1.2 Mecanismo de interacción CPU-memoria
Para leer datos, la CPU envía una señal de lectura por el bus de control, transmite la dirección por el bus de direcciones, y recibe los datos a través del bus de datos. Para escribir, la CPU envía una señal de escritura, proporciona la dirección y coloca los datos en el bus de datos.
Puntos clave:
- La memoria se compone de unidades de un byte.
- Cada unidad tiene una dirección única.
- Dirección = Puntero en C.
- Variables puntero y direcciones
2.1 Operador de dirección (&)
Para obtener la dirección de una variable se utiliza el operador &. Aunque una variable ocupe múltiples bytes (por ejemplo, un int de 4 bytes), solo se retorna la dirección del primer byte.
int main() {
int dato = 0x11223344;
printf("Dirección en memoria: %p\n", &dato);
return 0;
}
2.2 Variables puntero y operador de desreferencia (*)
2.2.1 Variables puntero
Una variable puntero almacena direcciones de memoria.
int main() {
int valor = 0x11223344;
int *puntero = &valor; // puntero almacena dirección de 'valor'
return 0;
}
El operador * indica que la variable es un puntero, y el tipo precedente (int) indica el tipo de dato al que apunta.
2.2.2 Desreferenciación
El operador * permite acceder al valor almacenado en la dirección apuntada.
int main() {
int numero = 10;
int *ptr = №
*ptr = 20; // Modifica el valor de 'numero' indirectamente
printf("Valor: %d\n", numero); // Muestra 20
return 0;
}
2.3 Tamaño de las variables puntero
El tamaño de un puntero depende de la arquitectura del sistema:
- En sistemas de 32 bits: 4 bytes (32 bits)
- En sistemas de 64 bits: 8 bytes (64 bits)
int main() {
char *pchar = NULL;
int *pint = NULL;
printf("Tamaño de puntero char*: %zu bytes\n", sizeof(pchar));
printf("Tamaño de puntero int*: %zu bytes\n", sizeof(pint));
return 0;
}
El tamaño es independiente del tipo de dato al que apunta.
- Significado del tipo de puntero
3.1 Desreferenciación según el tipo
El tipo determina cuántos bytes se modifican al desreferenciar.
// Modifica 4 bytes (int)
int main() {
int dato = 0x11223344;
int *ptr_int = &dato;
*ptr_int = 0;
return 0;
}
// Modifica solo 1 byte (char)
int main() {
int dato = 0x11223344;
char *ptr_char = (char*)&dato;
*ptr_char = 0;
return 0;
}
3.2 Aritmética de punteros
Al sumar o restar enteros a un puntero, el desplazamiento depende del tipo de dato.
int main() {
int arreglo[] = {10, 20, 30};
int *ptr_int = arreglo;
char *ptr_char = (char*)arreglo;
printf("ptr_int + 1: %p\n", ptr_int + 1); // Avanza 4 bytes
printf("ptr_char + 1: %p\n", ptr_char + 1); // Avanza 1 byte
return 0;
}
3.3 Punteros void*
Los punteros void* pueden almacenar cualquier dirección, pero no permiten desreferenciación directa ni aritmética de punteros sin conversión.
int main() {
int num = 42;
void *ptr_generico = #
// *ptr_generico = 10; // Error: no se puede desreferenciar void*
int *ptr_entero = (int*)ptr_generico;
*ptr_entero = 10; // Correcto
return 0;
}
- Restricción con const en punteros
El calificador const puede aplicarse a diferentes partes de un puntero:
const int *ptr: No se puede modificar el valor apuntado.int *const ptr: No se puede modificar la dirección almacenada en el puntero.const int *const ptr: Ni la dirección ni el valor pueden modificarse.
- Operaciones con punteros
5.1 Aritmética básica
// Recorrer arreglo usando puntero
int main() {
int datos[] = {5, 10, 15, 20};
int *ptr = datos;
for (int i = 0; i < 4; i++) {
printf("%d ", *ptr);
ptr++;
}
return 0;
}
5.2 Resta de punteros
La resta de dos punteros del mismo tipo retorna el número de elementos entre ellos.
int main() {
int valores[] = {2, 4, 6, 8, 10};
int *inicio = &valores[0];
int *fin = &valores[4];
printf("Diferencia: %ld elementos\n", fin - inicio);
return 0;
}
5.3 Comparación de punteros
Los punteros pueden compararse para determinar orden relativo.
- Punteros salvajes (wild pointers)
Un puntero salvaje apunta a una ubicación de memoria no válida o no inicializada.
int main() {
int *ptr_no_inicializado; // Puntero salvaje
int *ptr_valido = NULL;
// Solución: inicializar siempre los punteros
int dato = 42;
ptr_valido = &dato;
return 0;
}
- Prevención de punteros salvajes
- Inicializar punteros al declararlos (NULL o dirección válida).
- Verificar límites al recorrer arreglos.
- Establecer punteros no utilizados a NULL.
- Verificar la validez antes de desreferenciar.
- Evitar devolver direcciones de variables locales.
- Aserciones (assert)
La macro assert verifica condiciones durante la ejecución.
#include <assert.h>
void procesar_datos(int *datos, size_t cantidad) {
assert(datos != NULL);
assert(cantidad > 0);
// Procesamiento seguro
}
int main() {
int valores[] = {1, 2, 3};
procesar_datos(valores, 3);
return 0;
}
- Llamadas por valor y por referencia
En C, las funciones pueden recibir parámetros por valor o por referencia (usando punteros).
// Intercambio por referencia
void intercambiar(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
intercambiar(&x, &y);
printf("x=%d, y=%d\n", x, y);
return 0;
}