Académique Documents
Professionnel Documents
Culture Documents
Cada tipo de segmento pueden interpretarse como números, caracteres, imágenes, etc. A veces un tipo de segmento tiene varias
interpretaciones, por ejemplo, un segmento que contenga 1000001 puede ser interpretado como 65 10 o como 4116 o como la letra A; para
hacer esto, solo escribimos:
int n = 65;
Memoria RAM n
...01011 1000001 01100..
0x7ff...c (dirección de n en base hexadecimal)
En la realidad solo existen los números 0 y 1.
Las indicaciones en rojo no existen realmente, solo sirven para darnos información que nos ayuda a comprender lo que hay en la RAM.
En tiempo de ejecución, el sistema operativo, ubica a n en una posición disponible, por ejemplo, 0x7ff...c, es casi seguro que esta
posición cambie si se reprocesa el programa. El programador no asigna estas posiciones, solamente puede referirse a ellas, preparemos
las herramientas para ello. Ya hemos utilizado en modo limitado dos operadores:
Operador &: suministra la dirección de una variable, por ejemplo:
printf(“%p\n", &n);
Salida: 0x7ff...c
En resumen:
Operador Descripción
& Operador de dirección (referencia) : &n = dirección de n = 0x7ff...c
* Operador de indirección (dereferencia): *&n = valor de la dirección de n = 65
* es casi el inverso de &: *&n = *(&n) = n; pero &*n no compila.
Apuntador
Un apuntador ptr es una variable, como cualquier otra; ocupa 8 bytes de memoria; su formato de impresión es p: printf(“%p”, ptr);
almacena valores hexadecimales, los cuales son la dirección de otra variable, ejemplo, dada una variable:
int n = 65;
RAM ptr n
0x7ff...c 65
0x7ff...0 0x7ff...c
Tiene una dirección en la RAM = 0x7ff...0
ptr puede ser interpretado como un variable espía benévola de n, con las siguientes capacidades:
Sabe la dirección de n: ptr → 0x7ff...c
Sabe el valor de n : *ptr → 65
Puede cambiar el valor de n: *ptr = 66; // asigna 66 a n.
Jmm, estamos usando ptr, pero todavía no lo hemos definido, lo cual es un error. Definir a ptr no es tan sencillo, podríamos definir y
asignar valor a la variable ptr así:
pointer ptr = &n;
Para mayor precisión, podemos indicar que ptr guarda direcciones de variables de tipo int:
int pointer ptr = &n;
PÁGINA: 1
Lenguaje C
Lo mismo sucedería para otros tipos de variables, ejemplo:
float datoFloat;
float puntero ptrf = &datoFloat; // para cada tipo de dato se tendría un puntero
Se lee así:
El valor apuntado por ptr es de tipo int
int *ptr = &n;
valor de ptr = dirección de n
ptr = 0x7ff...C; // No compila porque no podemos manejar directamente a la memoria; esta es tarea del sistema operativo.
n = 0x6ff...c; // No compila , además n es de tipo entero, no hexadecimal
*ptr = 0x7ff...c; // No compila , además *ptr es de tipo entero.
No Opuestos para variables no apuntadoras : *&n = n = 65; pero &*n, no compila, ya que *n no funciona porque n no es una
posición de memoria.
p1 p2 n m
0x7ff...a 0x7ff...a 2 8
0x7ff...a
#include<stdio.h>
void main(void){
int n=2 , *p1= &n, *p2= &n, m = 8; // declaración y asignación de valores
if(p1==p2) printf("%d %d\n", *p1, *p2); // Igualdad de apuntadores
}
Salida:
2 2
Observaciones:
El valor de n puede cambiar de 3 modos:
n = 4;
*p1 = 4;
*p2 = 4;
Aritmética de punteros
Dada la naturaleza de un puntero -apunta a una dirección sobre una línea-, se puede desplazar hacia adelante o atrás, mediante sumas
y restas:
ptr; // == 0x7ff...0. Apunta a arr[0]
*ptr; // == 10
ptr + 1; // Apunta a la siguiente posición de tipo int: avanza 4 bytes: 0x7ff...4
// si ptr apuntara a un tipo long, sumará 8 bytes, para char sumará 1 byte; etc.
*(ptr+1); // == 20
ptr+n; // Apunta n posiciones más adelante
*(ptr+n); // valor n posiciones más adelante
*ptr++;
printf("%d\n", *ptr++); // printf("%d\n", *ptr); ptr++;
printf("%d\n",*(ptr++)); // printf("%d\n", *ptr); ptr++;
printf("%d\n", *(ptr+1)++); // error de compilación
PÁGINA: 4
Lenguaje C
Apuntador constante
int a=10, b=20;
int * const p = &a; // p apunta solo a la variable a.
*p = 15; // Correcto: asigna 15 a a, el valor apuntado es variable.
p=&b; // ERROR: El valor de p es constante
p a b
0x7ff...a 10 20
0x7ff...a
2) El valor de ptr y &ptr son diferentes; el valor de arr y &arr son iguales;
ptr tiene una dirección y apunta a otra dirección, no puede apuntarse a sí mismo
printf("%p\n", &ptr); // dirección de ptr: 0x7fa...b
printf("%p\n", ptr); // apunta a 0x7ff...0
PÁGINA: 5
Lenguaje C
3) La función sizeof() que da el tamaño de una variable en bytes, funciona de modo distinto, ejemplo:
#include <stdio.h>
void miFun(int arr[ ], int *ptr);
void main(void){
int arr[5]= {1,2,3,4,5}, *ptr = arr;
printf("%lu\n", sizeof(arr)); // salida 20 = 5*4, 4 es el sizeof de cada variable entera
printf("%lu\n", sizeof(ptr)); // salida 8, el sizeof de una variable de tipo apuntador.
miFun(arr, ptr);
}
void miFun(int arr[ ], int *ptr){
printf("%lu\n", sizeof(arr)); // salida 8, el sizeof de un apuntador, porque arr se comporta como apuntador
printf("%lu\n", sizeof(ptr)); // salida 8, el sizeof de un apuntador.
}
Ejemplos:
Arreglo de una dimensión: Crear un arreglo, reportarlo, sumarle 2 a cada elemento y reportarlo
// 06_01a.c : Notación de arreglos // 0601b.c : Notación de apuntadores
#include<stdio.h>
void reporte(int arr[], int n);
void suma2(int arr[], int n);
void main(void){
int n=10, arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("Datos iniciales\n");
reporte(arr, n);
suma2(arr, n);
printf("Datos aumentados\n");
reporte(arr, n);
}
void reporte(int arr[ ], int n){ // Equivalente: void reporte(int *arr, int n){ arr[ ] es un puntero
// Los desplazamientos son lógicos // Los desplazamientos son físicos
int i; int *p, *pmax = arr+n; // el puntero p es una variable de
//recorrido del arreglo de inicio a fin
for(i=0; i<n; i++) printf("%d ", arr[i]); for(p=arr; p<pmax; p++) printf("%d ", *p);
printf("\n");
}
void suma2(int arr[ ], int n){ // Equivalente: void reporte(int *arr, int n){ arr[ ] es un puntero
// Los desplazamientos son lógicos // Los desplazamientos son físicos
int i; int *p, *pmax = arr+n; // p es variable de recorrido
for(i=0; i<n; i++) arr[i] +=2; for(p=arr; p<pmax; p++) *p +=2;
}
Salida:
Datos iniciales
1 2 3 4 5 6 7 8 9 0
Datos aumentados
3 4 5 6 7 8 9 10 11 2
ATENCION, ATENCION, Gran cambio de modo de pensar: Hasta este momento, el uso de apuntadores es artificioso, problemático,
antipático; pero anímate, hay buenas noticias:
• Adaptación de la lógica al hardware: Manejo directo de la RAM, que es como una cinta larguísima que contiene 0s y 1s.
• Rapidez: Es más rápido trabajar sobre el hardware
• Potencia: Puedes definir datos complejos y manejarlos directamente con tus propias funciones, ejemplo: Puedes manejar
paramétricamente arreglos de más de una dimensión, cambiar el tamaño de los mismos; puedes tener arreglos grandes de
estructuras y visualizarlos ordenados por diferentes campos sin mover un dato de la estructuras: un gran arreglo de datos de los
estudiantes (código, nombre, notas, etc.) puede ser visto ordenado por código, nombre, etc. al mismo tiempo.
• Flexibilidad: Apunta a todo tipo de variable
Por lo anterior, de acá en adelante, preferiremos usar apuntadores para manejar arreglos de todo tipo de datos; considerando la
limitación: Un apuntador es apenas, una variable que apuntará al inicio del arreglo; el programador es responsable del control de las
dimensiones.
PÁGINA: 6
Lenguaje C
// 06_02.c : Dado un puntero float *f1. Verificar que al sumar n a f1 (f2 = f1 = n); la dirección aumenta a:
n*sizeof(float) bytes*/
#include<stdio.h>
void main(void){
float f, *f1 = &f, *f2;
int n = 2;
printf("\nPrimera dirección apuntada: %p (f1)\n", f1);
f2 = f1 + n;
printf("Segunda dirección apuntada: %p (f2 = f1 + n; n = 2)\n", f2);
printf(" --------------\n");
printf("Aumento de dirección : %lu bytes\n", (long int)f2 - (long int)f1);
printf("n*sizeof(float) = %d * %lu : %lu bytes\n", n, sizeof(float), n*sizeof(float));
printf("f2 - f1 : %lu posiciones\n", f2 -f1);
}
Salida:
Primera dirección apuntada : 0x7fff329c5ee0 (f1)
Segunda dirección apuntada: 0x7fff329c5ee8 (f1 + 2)
–-------------------
Aumento de dirección : 8 bytes
n*sizeof(float) = 2 * 4 : 8 bytes
f2 – f1 : 2 posiciones
Matriz Línea
j→
i 2 4 6 8 2 4 6 8 10 12 14 16
10 12 14 16
Cada fila i de la matriz aporta 4 datos a la línea + j que aporta j datos: la posición [i][j] ocupa la posición i*4+j en la línea.
En general, para un arreglo arr[m][n], el elemento arr[i][j] ocupa la posición líneal: i*n+j en la RAM.
Estos problemas se resuelven en modo óptimo trabajando con punteros, asignando memoria dinámicamente y controlando el número de
elementos en cada dimensión; lo cual haremos en dos capítulos más adelante; por el momento hagamos una aproximación trabajando
con arreglos y apuntadores:
Posiciones: 0 1 2 3 4 5
ptr arr[0][0] arr[0][1] arr[0][2] arr[1][0] arr[1][1] arr[1][2]
0x7234… 2 4 6 8 10 12
0x7234...
arr[i][j] es equivalente a *(ptr+ i*3+j) y a ptr[i*3+j]
PÁGINA: 7
Lenguaje C
La notación de punteros es engorrosa para referirse a los elementos de un arreglo; pero su uso permite parametrizar todas las
dimensiones.
Arreglo de apuntadores
Un puntero es una variable como cualquier otra, por lo tanto podemos tener arreglos de punteros, ejemplo:
int *ptr[4], n = 6;
ptr[0] = ptr[1] = &n;
*ptr[1]; // = 6
ptr n
0xff..c 0xff..c 6
Posiciones 0 1 2 3 0xff..c
Apuntador a apuntador
Un puntero es una variable como cualquier otra, por lo tanto podemos definir un puntero a puntero:
Sintaxis:
tipo **nombreApuntador;
ejemplo:
int a = 3, *p1 = &a, **p2 = &p1;
p2 p1 a
0x7234.. 0x8174.. 3
0x65734.. 0x7234.. 0x8174..
Ejemplos:
int *p[2];
* y [2] son adyacentes a p, [2] tiene mayor prioridad que * (regla 2)
→ arreglo de 2 punteros a enteros.
int (*p)[2];
(*p) Es un puntero
(*p)[2] Es un puntero a un arreglo de 2 elementos de tipo entero
Ejemplos
// 06_04.c: 4 formas, entre otras, para acceder a los datos de una matriz utilizando punteros.
// Aplicación de las precedencias de *, () y [] para punteros
#include<stdio.h>
void main(void){
int i, j, a[3][2] = {1, 2, 3, 4, 5, 6};
int *p1[6]; int (*p2)[2]; int *p3; int *p3, **p4;
// arreglo de punteros // puntero a arreglo de 2 elementos // puntero a entero // puntero a puntero entero
printf("Arreglo de punteros\n"); printf("\nPuntero a arreglo\n"); printf("\nPuntero a entero\n"); printf("\nPuntero a puntero\n");
p2 = a;
for(i=0; i<3; i++) for(i=0; i<3; i++) for(i=0; i<3; i++) { for(i=0; i<3; i++) {
p3 = a[i]; p3 = a[i];
p4 = &p3; // Apunta a
//arreglo de 2 elementos
for(j=0; j<2; j++) { for(j=0; j<2; j++) for(j=0; j<2; j++) for(j=0; j<2; j++)
p1[i*2+j] = &a[i][j];
printf("%d\n", *p1[i*2+j]); printf("%d\n", p2[i][j]); printf("%d\n", p3[j]); printf("%d\n", (*p4)[j]);
} } }
} } } }
Salida: 1 2 3 4 5 6
Apuntador a void
La palabra reservada void (se pronuncia void en inglés y significa vacío, nulo, vacante, nada, ...) se utiliza para crear un puntero que
apuntará a un tipo de dato genérico, para usarlo se requiere hacer casting al apuntador, ejemplo para int: pInt = (int *)pVoid;
int in=1;
double db=2.4;
void *pvoid; // un apuntador a void puede apuntar a cualquier tipo de dato:
PÁGINA: 10
Lenguaje C
Ejemplo: La función free( ), para liberar memoria dinámica apuntada por un puntero p de cualquier tipo, tiene la sintaxis:
void free(void *ptr);
Se puede usar:
free(p); // p es un apuntador a cualquier tipo de dato, free() libera la memoria y asigna p = NULL.
¡Apuntaste bien!!!!
Origen: https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQFyz_WY0xkicaiWFizB_rnTM9D-9X8wB9QhPv5jS2h4mQGdPMC6g
Si fallas en punteros, cometerás el famoso error: “violación de segmento", que es difícil de corregir, ya que no se sabe donde ocurrió, te
parecerá que la máquina se volvió loca; para identificar donde perdiste la puntería puedes ayudarte escribiento trazas, ejemplo:
printf(“Estoy en AA %d\n", n); // valor de n en la línea AA
printf(“Estoy en BB %d\n", n); // valor de n en la línea BB
Usando variables de tipo puntero -muy importantes, potentes y peligrosas- pasas del manejo estático (en tiempo de programación) de la
RAM, al manejo dinámico (en tiempo de ejecución). Toda la teoría está bien organizada y completa; para la práctica te recomiendo: 1)
Enamórate del tema y cuéntale a tu novi@ para que no se ponga celos@, 2) Haz una sesión personal especial y trata de entender todos
los detalles, y 3) Reúnete con los amigos para tirar flechas, perdón, para resolver problemas de apuntadores. Ya casi eres un
profesional.
Ejercicios:
1) Para un arreglo tridimensional, por ejemplo:
int a[2][3][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} ;
¿Que posición lineal ocupará a[i][j][k]?
2) Una matriz p[4][12] representa una producción de los 12 meses en 4 años. Lea la matriz y use punteros para calcular el promedio de
producción en cada año y en los 12 meses:
Años
1 2 3 4
Promedio: xx xx xx xx
Meses
1 2 3 4 … 12
Promedio: xx xx xx xx xx
PÁGINA: 11