Coco129:
Esto que a contnuación voy a poner, es una adaptación de un "cursillo" que doy sobre microcontroladores programados con lenguaje C. Según mi opinión, es más fácil comprender un concepto si pudes visualizarlo. KnowDemon te ha dado una buena explicación acerca de los conceptos, así que trataré de complementarlo un poco.
Sea dado el siguiente fragmento de código:
Código:
#include <stdio.h>
int main(){
unsigned int X = 0x4847463B;
unsigned int Y = 0x44434241;
unsigned int *pToInt = &X;
char szCita[] = "En un lugar de la..";
char *pToChar = szCita;
unsigned int i = 0;
...
Ignoremos por el momento lo que seguiría a continuación de las declaraciones de las anteriores variables, y veamos que pasa en memoria:
La anterior imagen representa como se acomodarían las variables en el stack para poder ser utilizadas a partir de una dirección ficticia (FF60), la línea inferior del grafico es sólo para dar idea de como se van acomodando los bytes y como va el conteo de los mismos. Según el compilador que uses, puede haber variaciones en el acomodo, pero ignoremos eso por el momento. Aclaro además, que en los procesadores x86, el stack crece hacia abajo (por ridícula que suene la expresón).
Lo importante aquí, es que se acomodan en el orden en que han sido declaradas. Ahora, nos interesa en particular
pToInt. Esta variable ha sido declarada como un puntero a un integer, y ha sido inicializada con la dirección de la variable X.
La variable X, está en la dirección FF60.
Por lo tanto, el valor almacenado en pToInt es 0x0000FF60.
Znet comentaba que un puntero "lo usas de solo lectura". Pero un puntero es al fin y al cabo una variable. Y para ser exactos es una variable de tipo integer (en procesadores de 32 bits). Por lo tanto, en un puntero se pueden realizar operaciones como se haría con cualquier otra variable numérica.
¿Que pasaría con la sentencia "
pToChar++"? Nota que no estoy usando el operador de indirección.
Simplemente accederiamos a la siguiente posición de memoria.
Código:
//Hasta 18, ya que sólo tenemos 19 caracteres y un null
for(i=0; i<=18; i++){
printf("%c", *(pToChar++));
}
//O también
for(i=0; i<=18; i++){
printf("%c", *(pToChar + i));
}
//Ambas sentencias equivalen a
for(i=0; i<=18; i++){
printf("%c", szCita[i]);
}
¿Que pasaría con la sentencia "
pToInt--"?
Puesto que ahora contiene el valor 0x0000FF60, después de la operación pToInt--, pToInt contendrá 0x0000FF5C. ¡Un momento! ¡Pero si sólo lo decrementamos en 1! La diferencia entre FF60 y FF5C es de 4 ¿qué pasó?
Es probable que hayas leído en algún libro de C, o tal vez en clases habrás oído que un puntero declarado para cierto tipo de dato, sólo puede apuntar a variables del mismo tipo de dato. Por ejemplo un puntero de tipo char no puede apuntar a una variable integer. ¿Pero por qué? Porque cuando el programa se compila, a nivel de procesador, el puntero es una variable que es referenciada con un offset fijo. Un puntero de tipo Integer, solo referenciará posiciones de memoria multiplos de 4, un puntero de tipo short integer multiplos de 2, un puntero de tipo char podrá referenciar cualquier posición de memoria. Pero independientemente de ello, todos ocuparan la misma cantidad de memoria.
Entonces, la sentencia "
pToInt--" podría ser interpretada como "asignar a pToInt la dirección del integer inmediatamente inferior al que actualmente apunta".
Hagamos ahora algo que pondría los pelos de punta a mi antiguo maestro de programación. Asignemos a un puntero de tipo integer, la dirección de memoria de un char.
¿Qué no se puede? ¡Por supuesto que sí! ¿Qué para que querría hacer alguien semejante cosa? Es cuestión de la imaginación de cada loco, pero por lo menos, en la programación de microcontroladores, esto me ha sacado más de una vez de apuros. Y también lo he usado en programación de Windows, para ahorrarme trabajo. Y ahora lo haremos sólo para demostrar cuán poderosos pueden ser los punteros.
Puesto que los punteros, independientemente del tipo de dato al que apunten son del mismo tamaño, la siguiente sentencia es perfectamente válida.
pToInt = (unsigned int *) szCita;
¿Qué acabamos de hacer? Acabamos de lograr que el espacio definido en memoria como char, se comporte como integer. Si trazamos el valor, veremos que en Hexadecimal equivale a 0x75206E45, que resultan ser los primeros cuatro caracteres de nuestra cadena.
Ya que estamos "encarrerados", asignemos a un puntero de tipo char una variable de tipo integer. Pero elevemos la emoción un grado más. Vamos a obtener e imprimir la cadena de caracteres que está contenida en las variables X e Y. Los numeros contenidos en dichas variables no han sido declarados de manera casual. X contiene el equivalente en memoria de ['H','G','F',';'] e Y el equivalente de ['D','C','B','A']
Código:
pToInt = &Y
*pToInt += 10;
pToChar = (char *) &Y;
for(i=0; i<=7; i++){
printf("%c", *(pToChar + i));
}
Lo anterior imprimira en pantalla, "ABCDEFGH"
A continuación te dejo un pequeño código con los experimentos que arriba meciono. Espero que no te haya confundido, y que esto no haya sdo aburrido. Pero la verdad es que creo que nadie comprende realmente los punteros hasta que aprende algo de ensamblador. Bueno, es sólo mi opinión.
Código:
#include <stdio.h>
int main(){
using namespace std;
unsigned int X = 0x4847463B;
unsigned int Y = 0x44434241;
unsigned int *pToInt = &X;
char szCita[] = "En un lugar de la..";
char *pToChar = szCita;
unsigned int i = 0;
//Mostramos los valores
printf("X = %d \n", X);
printf("Y = %d \n", Y);
printf("szCita = %s \n\n\n", szCita);
//Apuntadores de la variables
printf("Addr de X es: %d \n", &X);
printf("Addr de Y es: %d \n", &Y);
printf("Addr de pToInt es: %d \n", &pToInt);
printf("Addr de szCita es: %d \n", szCita);
printf("Addr de pToChar es: %d \n", &pToChar);
printf("Addr de i es: %d \n\n\n", &i);
//Operaciones sobre X mediante el puntero
printf("Valor de X mediante *pToInt\n");
printf("*pToInt = %d \n\n", *pToInt);
printf("Aumentamos el valor de X mediante *pToInt\n");
printf("X = %d\n", X);
printf("*pToInt+=10\n");
*pToInt+=10;
printf("X = %d\n\n\n", X);
/*
Decrementamos el valor del puntero. No el valor de la variable
referenciada, sino el valor de la dirección almacenada en el
puntero. Esto es algo catalogado como "peligroso", pero si se
sabe lo que se está haciendo puede ser útil en algunas situaciones.
En este caso, sabemos que las variables X e Y están colocadas en
posiciones adyacentes de la memoria, y puesto que pToInt apunta a
X, decrementarlo en 1, hará que apunte ahora a Y.
*/
pToInt--;
//Operaciones sobre Y mediante el puntero
printf("Valor de Y mediante *pToInt\n");
printf("*pToInt = %d \n\n\n", *pToInt);
//Operaciones sobre el string de modo normal
printf("Imprimimos el contenido del string iterando\n");
printf("en cada uno de sus elementos...\n");
for(i=0; i<=18; i++){
printf("%c", szCita[i]);
}
printf("\n\n");
//Operaciones sobre el string con el puntero
printf("Imprimimos el contenido del string incrementando\n");
printf("el valor del puntero pToChar...\n");
for(i=0; i<=18; i++){
printf("%c", *(pToChar++));
//printf("%c", *(pToChar + i));
}
printf("\n\n\n");
/*
Asignar un puntero de un tipo a otro de tipo distinto,
es algo "peligroso", pero perfectamente posible.
*/
//Dirección de un integer a un puntero de tipo char
pToChar = (char *) &Y; //Usamos casting para que se pueda compilar
for(i=0; i<=7; i++){
printf("%c", *(pToChar + i));
}
printf("\n\n\n");
//Direccion de la cadena a un puntero de tipo integer
pToInt = (unsigned int *) szCita;
printf("Hex: 0x%x \n", *pToInt);
getchar();
return 0;
}